Keycloak Redirect URI Change: Fix HTTP Parameter Pollution

Guilliano Molaire Guilliano Molaire 11 min read

Keycloak 26.8.0 rejects any redirect_uri or post_logout_redirect_uri that already contains an OIDC response parameter such as state, code, session_state or iss, whether it sits in the query string or in the fragment. The rule is older than 26.8, though: the query-string check first appeared in the July 2026 releases (26.7.0, 26.6.5 and 26.4.14), and 26.7.3 and 26.6.7 later extended it to fragments and added a temporary opt-out. A request that trips the check fails with an invalid redirect URI error, so the fix is to clean up the redirect URIs your apps send, because the opt-outs are deprecated and will be removed in Keycloak 27.

The reason for the change is HTTP Parameter Pollution (HPP), which means sending the same parameter name more than once in a request so that different components can read different values. After a login, Keycloak appends code, state, session_state and iss to your redirect URI. If the URI already has a state in it, the browser arrives at your app with two state values, and which one your framework picks is an implementation detail you probably never tested. This post covers what changed in each release line, how to find the clients and apps that break, and how to migrate them before the opt-out disappears.

If you are chasing an “Invalid parameter: redirect_uri” error that has nothing to do with OIDC parameters, start with our redirect URI mismatch troubleshooting guide instead.

Key takeaways

  • Keycloak rejects redirect URIs containing any of 13 OIDC response parameter names, matched case-insensitively (Keycloak RedirectUtils.java, 26.8.0 source, 2026).
  • Query-string rejection began in 26.7.0, 26.6.5 and 26.4.14 in July 2026. Fragment checks and the opt-outs came in 26.7.3 and 26.6.7, and 26.8.0 carries both.
  • The server and per-client opt-outs are deprecated and will be removed in Keycloak 27, so use them only as a bridge.

Which Keycloak versions reject OIDC parameters in redirect URIs?

In 2026, the change reached the supported release lines in two steps, and we traced both through the Keycloak source at each release tag. The first step added a query-string check with no way to turn it off. The second step, documented in the upgrading guide, extended the check to the URI fragment and added deprecated switches to restore the old behaviour while you migrate.

Release line Query-string check since Fragment check and opt-out since
26.8 26.8.0 (1 October 2026) 26.8.0
26.7 26.7.0 (9 July 2026) 26.7.3 (31 August 2026)
26.6 26.6.5 (24 July 2026) 26.6.7 (7 September 2026)
26.4 26.4.14 (24 July 2026) Not in 26.4.16, the latest 26.4 tag we checked

The dates are the release tag dates. Two things in the table are easy to miss. The 26.4 line rejects query-string OIDC parameters but, as of 26.4.16, has neither the fragment check nor any opt-out, so on 26.4 the only fix is in the client. And the upgrading guide only documents the change from 26.7.3 and 26.6.7 onwards, so if a login started failing after a July patch, this check may be the cause even though the upgrading guide did not mention it at the time.

What does the redirect URI check actually do?

In 2026, the Keycloak upgrading guide added an entry titled “OIDC parameters in redirect URIs rejected by default” to the notes for 26.7.3 and 26.6.7. It says redirect URIs “containing OIDC response parameters such as state, code, or session_state are now rejected by default” because they “conflict with the parameters that Keycloak attaches to the redirect URI after authentication or logout, potentially causing HTTP Parameter Pollution.”

The source code gives the full list. In RedirectUtils.java at tag 26.8.0, the forbidden set contains code, id_token, access_token, token_type, expires_in, state, iss, error, error_description, session_state, response, kc_action and kc_action_status. The check decodes the query string and the fragment of the redirect URI that the client sent, compares each parameter name with that set without regard to case, and rejects the URI on any match. It runs before Keycloak compares the URI with the client’s registered redirect URIs, so a valid registration does not rescue a request that carries one of these names. The same check applies to post_logout_redirect_uri at the logout endpoint.

Other query parameters are unaffected. A redirect URI such as https://app.example.com/callback?tenant=acme still works, as long as it matches a registered redirect URI.

When the check fires, Keycloak logs a warning like this one, which is the quickest way to tell it apart from an ordinary mismatch (releases before 26.7.3 and 26.6.7 say “in query string” rather than “in query”):

Redirect URI rejected: contains forbidden OIDC parameter 'state' in query: scheme=https, host=app.example.com, path=/callback

The login itself fails with the invalid_redirect_uri event error, the same one a typo in the registration produces, which is why this change is easy to misdiagnose.

Why is HTTP parameter pollution a risk in OAuth callbacks?

HTTP Parameter Pollution is a long-documented web weakness, and the OWASP Web Security Testing Guide has a dedicated test for it: send the same parameter name more than once and watch how each component handles the copies. There is no standard for which copy wins. Some frameworks take the first value, some take the last, and some join them with a comma, so an OAuth callback that receives two state values may validate a different one than you expect.

The state parameter is how your app ties the authorization response back to the request it started, and RFC 6749, the OAuth 2.0 framework from 2012, recommends it as the defence against cross-site request forgery on the redirect endpoint. If a redirect URI already carries a state and Keycloak appends the real one, the outcome depends on parsing rules rather than on your security logic. The same applies to code, iss and session_state, which Keycloak appends after login, and to kc_action_status, which it appends after an application-initiated action.

It is worth being precise about the threat. An attacker cannot make Keycloak redirect to a URI that does not match a registration, so the realistic risk comes from broad wildcard registrations and from apps that build callback URLs carelessly. Keycloak is also stricter than the specification here: section 3.1.2 of RFC 6749 allows a redirect URI to include a query component and says it must be retained when parameters are added. Keycloak still allows query components in general and only refuses the names that OIDC responses and Keycloak actions use.

Which apps and clients are likely to break?

Three patterns account for most failures. The upgrading guide names the first, and the other two are what we would look for first in any estate with older or hand-built clients.

  1. A post_logout_redirect_uri that points straight at the Keycloak login page. Some apps log the user out and send them directly to https://<host>/realms/<realm>/protocol/openid-connect/auth?response_type=code&client_id=..., so the user lands on a fresh login screen. That URL is rejected if it contains any of the 13 names, which in practice usually means state. The guide’s fix is to point post_logout_redirect_uri at a callback in your own app, such as https://myapp/logout-callback, and have that route start a normal login.
  2. Callback URLs that reuse a reserved name for the app’s own purposes. An app that writes /callback?state=dashboard to remember where to send the user after login collides with the OIDC state. That value belongs inside the OIDC state itself, or in session storage keyed by it, rather than in the redirect URI.
  3. Broad wildcard registrations. A registered redirect URI such as https://app.example.com/* matches almost anything, which can hide a client that appends ?code= or #state= to its own redirect URI. Those requests are now rejected even if the registration has not changed.

How do you find affected clients before you upgrade?

The check runs against the URI that clients actually send, so the most reliable inventory comes from live traffic. On a test environment running a release from the table above, turn on user events for the realm (Realm settings, then Events, then User events settings) and look for LOGIN_ERROR and LOGOUT_ERROR events with the error invalid_redirect_uri. Each event records the client_id and the full redirect_uri that was rejected, which the server log does not: the log line only gives the parameter name, scheme, host and path. Our guide to Keycloak auditing and event logging covers getting those events somewhere searchable.

You can also check the registered values ahead of time. After logging the admin CLI in with kcadm.sh config credentials, this pipeline prints the line number of every registered redirect URI or client attribute that hard-codes one of the 13 names:

kcadm.sh get clients -r myrealm --fields clientId,redirectUris,attributes 
  | grep -n -iE '(?|&|#)(code|id_token|access_token|token_type|expires_in|state|iss|error|error_description|session_state|response|kc_action|kc_action_status)='

The attributes field is included because post-logout redirect URIs are stored in the post.logout.redirect.uris client attribute. This only catches registrations that contain a forbidden parameter, so treat it as a first pass and rely on the events for the URIs your apps build at runtime. On the app side, search your code for logout handlers that build a Keycloak auth URL, and for callback routes that put state or code in the URL for their own use.

For the upgrade itself, the order we would follow is the one in our Keycloak upgrade strategy post: upgrade a staging cluster first, run every app’s login and logout path against it, fix what breaks, and then roll production.

How do you fix redirect URIs that carry OIDC parameters?

The fix depends on which pattern you found, and in each case the change belongs in the app rather than in Keycloak.

  • Logout that lands on the login page. Add a logout callback route to your app (for example /logout-callback), register it under the client’s “Valid post logout redirect URIs”, and make the route redirect to your normal login entry point. Your app then builds a fresh authorization request with a new state and PKCE verifier.
  • App state in the callback URL. Move it into the OIDC state value, or store it server-side or in session storage keyed by state, and keep the callback URL fixed. Most OIDC client libraries support this, often as a “return to” or “app state” option.
  • Broad wildcards. Replace https://app.example.com/* with the exact callback paths your app uses. RFC 9700, the OAuth 2.0 Security Best Current Practice published in January 2025, requires exact string matching of redirect URIs, with an exception only for the port number of localhost redirect URIs in native apps. Our PKCE client setup guide shows where those settings live in the admin console.

If a client uses response_mode=fragment, also check that nothing adds a fragment of its own to the redirect URI, because on 26.6.7, 26.7.3 and 26.8.0 the fragment is checked too.

Should you use the temporary opt-out?

Use it only as a bridge, and only for the clients that need it. On 26.6.7, 26.7.3 and 26.8.0 the upgrading guide documents two switches: the allow-oidc-params-in-redirect-uris option on the OpenID Connect login protocol provider, which restores the old behaviour for the whole server, and the allow.oidc.params.in.redirect.uris client attribute, which does it for one client. The guide states that “both options are deprecated and will be removed in Keycloak 27”, and the server logs a deprecation warning at startup while the server-wide option is on. The 26.4 line has no equivalent switch, so on 26.4 you have to fix the client.

The server-wide option is set like any other SPI option, and on 26.x the CLI form uses a double dash between the SPI, provider and option names:

bin/kc.sh start --spi-login-protocol--openid-connect--allow-oidc-params-in-redirect-uris=true

The per-client attribute is the better bridge because it keeps the protection on for every other client. You can set it through the Admin REST API or with the admin CLI:

kcadm.sh update clients/<client-uuid> -r myrealm 
  -s 'attributes."allow.oidc.params.in.redirect.uris"=true'

Keep a list of every client you set it on, with an owner and a target date, and remove the attribute once the app ships its fix. Any client still depending on it when you move to Keycloak 27 will fail at login after that upgrade.

Does this affect managed Keycloak?

Skycloak runs upstream Keycloak, so the same check applies to any cluster on a release listed in the table above. A managed service changes who tracks the release notes and when the cluster version moves, but your applications’ redirect URIs and logout routes are still your code, so the client inventory and fixes above apply whether you run Keycloak yourself or use managed Keycloak hosting. If you are weighing that choice more generally, our look at whether self-hosting Keycloak is worth it covers the trade-offs.

FAQ

When did Keycloak start rejecting OIDC parameters in redirect URIs?

The query-string check first appeared in Keycloak 26.7.0, tagged on 9 July 2026, and in the 26.6.5 and 26.4.14 patches tagged on 24 July 2026. Keycloak 26.7.3 and 26.6.7 extended it to the URI fragment, added the opt-outs and documented the change. Keycloak 26.8.0, tagged on 1 October 2026, includes all of it.

What error does Keycloak show when a redirect URI contains state?

The user sees the standard invalid redirect URI error and the event records invalid_redirect_uri, exactly as for a mismatch. The server log tells them apart with a warning that reads “Redirect URI rejected: contains forbidden OIDC parameter”, naming the parameter. The user event is where you find the client ID and the full rejected URI.

Can I still use custom query parameters in my redirect URI?

Yes, as long as their names are not on the forbidden list. In Keycloak 26.8 that list is code, id_token, access_token, token_type, expires_in, state, iss, error, error_description, session_state, response, kc_action and kc_action_status, matched without regard to case. Parameters such as tenant or lang work if the full URI matches a registered redirect URI.

How do I fix a logout that redirects straight to the Keycloak login page?

Point post_logout_redirect_uri at a route in your own app, such as /logout-callback, register that URL under “Valid post logout redirect URIs”, and have the route start a normal login. The Keycloak upgrading guide recommends this approach, and it means each new login gets a fresh state and PKCE pair.

When will the allow-oidc-params-in-redirect-uris option be removed?

The upgrading guide says both the server option and the allow.oidc.params.in.redirect.uris client attribute will be removed in Keycloak 27, and it gives no date. Treat the opt-out as a short-term bridge and keep a list of every client that uses it. If you are also seeing redirect loops after login, see our login loop troubleshooting guide.

Sources

  • Keycloak, Upgrading Guide, changes in 26.7.3, “OIDC parameters in redirect URIs rejected by default” (source at tag 26.8.0), retrieved 2026-10-03, https://github.com/keycloak/keycloak/blob/26.8.0/docs/documentation/upgrading/topics/changes/changes-26_7_3.adoc
  • Keycloak, Upgrading Guide, changes in 26.6.7 (source at tag 26.6.7), retrieved 2026-10-03, https://github.com/keycloak/keycloak/blob/26.6.7/docs/documentation/upgrading/topics/changes/changes-26_6_7.adoc
  • Keycloak, RedirectUtils.java at tag 26.8.0 (forbidden parameter list and check), retrieved 2026-10-03, https://github.com/keycloak/keycloak/blob/26.8.0/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
  • Keycloak, RedirectUtils.java at tags 26.7.0, 26.6.5 and 26.4.14 (first releases with the query-string check), retrieved 2026-10-03, https://github.com/keycloak/keycloak/blob/26.7.0/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java
  • Keycloak, OIDCLoginProtocolFactory.java at tag 26.8.0 (allow-oidc-params-in-redirect-uris option and deprecation warning), retrieved 2026-10-03, https://github.com/keycloak/keycloak/blob/26.8.0/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
  • OWASP, Web Security Testing Guide, “Testing for HTTP Parameter Pollution”, retrieved 2026-10-03, https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/04-Testing_for_HTTP_Parameter_Pollution
  • IETF, RFC 6749, “The OAuth 2.0 Authorization Framework”, 2012, https://datatracker.ietf.org/doc/html/rfc6749
  • IETF, RFC 9700, “Best Current Practice for OAuth 2.0 Security”, January 2025, https://datatracker.ietf.org/doc/html/rfc9700

The cluster, without the on-call

Skycloak runs real upstream Keycloak in the region you choose, and takes the upgrades, backups, certificate rotation and patching off your team. No fork, so you can export and self-host whenever you want.

Guilliano Molaire
Written by
Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Start Free Trial Talk to Sales
© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman