AppSec · Jul 29, 2026 · 7 min read

OIDC misconfiguration: when the auth flow leaks the token

Redirect URI bypass, missing state, PKCE downgrade and implicit flow each carry a distinct confirmation path - probing one without the others leaves real gaps open.

OpenID Connect is the auth layer for most modern applications - and the most common misconfigurations live in the flow itself, not in the cryptographic algorithm choices. Redirect URI validation, state parameter handling, PKCE enforcement and implicit flow token placement each carry a distinct confirmation path. Checking one without the others leaves real authorization bypass gaps open.

01Redirect URI validation: exact match is the only safe answer

The authorization server delivers the authorization code to whatever redirect_uri the client sends in the authorization request. If the server validates by prefix rather than exact equality, a small variation routes that code to an attacker-controlled host before the client application ever sees it. Common bypass shapes include path normalization (a registered URI of https://app.example.com/callback matched against https://app.example.com/callback/../attacker.io/x) and wildcard subdomain configurations where any subdomain of the registered domain is accepted - including one an attacker can register.

Legitimate authorization flow client ──► /authorize?redirect_uri=https://app.example.com/callback IdP ──► https://app.example.com/callback?code=AUTH_CODE OK Prefix-match bypass client ──► /authorize?redirect_uri=https://app.example.com/callback%2F..%2F..%2Fattacker.io%2Fx IdP ──► https://attacker.io/x?code=AUTH_CODE code delivered to attacker Wildcard subdomain bypass registered: *.example.com probe URI: https://attacker.example.com/steal IdP ──► https://attacker.example.com/steal?code=AUTH_CODE
Three bypass shapes, each requiring its own probe. A single exact-match test misses the wildcard and path-normalization variants; probing all three is the only way to confirm the surface is closed.

02State parameter CSRF and nonce replay

The state parameter is the CSRF token of the OAuth/OIDC authorization flow. A client that initiates the authorization request without a state value - or that accepts any state value on the callback without verifying it matches what it sent - cannot detect a cross-site request forgery where an attacker injects their own code into the victim's session. The nonce parameter provides the same replay protection for ID tokens: a nonce embedded in the authorization request must appear in the returned ID token, preventing a token issued to one session from being replayed in another. Both checks are client-side responsibilities; the authorization server can only require that the fields are present, not that the client validates them.

state-nonce-probe.httphttp
# Baseline: proper authorization request
GET /authorize
  ?response_type=code
  &client_id=app_client_123
  &redirect_uri=https://app.example.com/callback
  &state=xK9mLp3Qr7vNsT1w
  &nonce=yZ2wBd8Fj1uHcE9p

# State-absent CSRF probe: omit state entirely
GET /authorize
  ?response_type=code
  &client_id=app_client_123
  &redirect_uri=https://app.example.com/callback
# IdP proceeds; callback fires with code but no state
# If the client accepts the callback, CSRF protection is absent

# Nonce replay probe: reuse a nonce from a prior ID token exchange
# If the IdP accepts the replayed nonce, session binding is broken

03PKCE downgrade and implicit flow token placement

PKCE (RFC 7636) protects the authorization code flow for public clients by binding the code to the original request: the client generates a random code_verifier, hashes it to a code_challenge sent in the authorization request, and must present the verifier to exchange the code for tokens. A server that makes code_challenge optional allows any client to exchange an intercepted code without proving ownership of the original request - the PKCE guarantee is gone for any client that omits the parameter. The implicit flow (response_type=token) avoids this entirely by putting the access token directly in the URL fragment, where it is visible in browser history, referrer headers, and CDN or proxy logs. RFC 9700 prohibits implicit for this reason; the exposure surface is structural, not incidental.

Proven - CriticalRedirect URI prefix bypass - /oauth/authorize
GET /authorize?response_type=code&client_id=app_client_123 &redirect_uri=https://app.example.com/callback%2F..%2Fattacker-probe.example.net%2Fx ──► 302 https://attacker-probe.example.net/x?code=AUTH_CODE_62615533 POST /oauth/token code=AUTH_CODE_62615533 &redirect_uri=https://attacker-probe.example.net/x &grant_type=authorization_code ──► 200 OK {"access_token": "eyJ..." (masked), "token_type": "Bearer"} Code exchanged to a valid access token via the bypassed redirect target.
Oracle: code exchange succeeds from the attacker-controlled probe URI. A controlled test account was used; no real user session was touched; the access token is masked in stored evidence. Severity critical - the authorization code delivers a valid access token to an external endpoint with no legitimate claim on it.

04Severity follows confirmed token impact

Redirect URI bypass that delivers an authorization code which successfully exchanges for an access token is critical - the attacker gains a real credential for the victim's account, confirmed in the probe by the token exchange response. PKCE downgrade is high because it requires an interception position on the redirect channel; the code is not yet a token without a successful exchange, but a network attacker with access to the redirect traffic can complete that exchange. State-absent CSRF is high when the flow binds a privileged account (admin scope, OAuth delegation for account management) and medium otherwise. Implicit flow token leakage is rated by what the token can access: a read-only scope token in a referrer header is medium; an admin-scoped token in browser history is critical. Reachable findings - where the misconfiguration is confirmed present but token delivery to an attacker was not demonstrated - are rated one step below the confirmed equivalent.

MisconfigurationConfirmation methodSeverity
Redirect URI prefix or wildcard bypassCode delivered to controlled probe URI; exchanges for a valid access tokenCritical
State parameter absent or not validatedCallback accepted with mismatched or missing state; flow completesHigh (privileged scope) / Medium
PKCE not enforcedCode exchanges without code_verifier; token returnedHigh
Implicit flow enabledToken appears in URL fragment; referrer header leakage confirmedMedium - Critical (scope-dependent)
Nonce not validated client-sideReplayed ID token accepted; session binding absentHigh
5
distinct OIDC misconfiguration classes
0
real user sessions touched during probes
1
controlled test account per authorization flow

Probes stay safe throughout: each authorization flow is initiated with a controlled test account registered with the application. The redirect URI in bypass probes points to an endpoint the engine controls - no real user token is exfiltrated and no real user is affected by the code exchange. The access token returned in a confirmed finding is masked in stored evidence. The fix is on the authorization server: enforce exact-match redirect URI comparison (not prefix or wildcard), require and validate state on every authorization response, make PKCE mandatory for all client types including confidential clients, and remove implicit flow support from the server configuration entirely rather than relying on each client to opt out.

Evidence over heuristicsReachable beats foundMonitor → Block: rolling out a gate without slowing teamsIaC-grounded threat modelingAir-gapped AppSec with no phone-homeOne platform vs three tools
See it on your own app →