OpenID Connect Security Testing: OAuth 2.0 Identity Layer Vulnerabilities
OpenID Connect (OIDC) is the authentication layer built on top of OAuth 2.0. Where OAuth answers "what can this application access?", OIDC answers "who is this user?" It is the protocol behind every "Sign in with Google," "Sign in with GitHub," and enterprise SSO integration that doesn't use SAML.
Most modern web applications implement OIDC — often without their engineering teams fully understanding the security boundaries. The vulnerabilities aren't exotic: they're misconfigurations in redirect URI validation, missing nonce checks, weak state parameter handling, and ID token validation shortcuts that seemed reasonable at the time.
This guide covers OIDC from a penetration testing perspective: the protocol flow, where it breaks, and what to test.
How OpenID Connect Works
OIDC adds an identity layer to OAuth 2.0 by introducing the ID token — a JWT issued by the Identity Provider (IdP) that contains claims about the authenticated user. The most common flow is the Authorization Code Flow:
- Authorization request. The relying party (your application) redirects the user to the IdP's authorization endpoint with parameters:
response_type=code,client_id,redirect_uri,scope=openid,state, andnonce. - User authenticates. The IdP authenticates the user and obtains consent if needed.
- Authorization code. The IdP redirects back to the application's
redirect_uriwith an authorization code and thestateparameter. - Token exchange. The application exchanges the authorization code for tokens at the IdP's token endpoint (server-to-server). It receives an ID token, access token, and optionally a refresh token.
- ID token validation. The application validates the ID token's signature, issuer (
iss), audience (aud), expiry (exp), and nonce. It extracts the user identity from claims likesubandemail.
The security of this flow depends on every step being implemented correctly. Each step has failure modes.
Redirect URI Validation Flaws
The redirect_uri is where the authorization code gets delivered. If an attacker can manipulate it to point to a URI they control, they intercept the code and can exchange it for tokens.
Common validation failures:
- Prefix matching only. The IdP validates that the redirect URI starts with a registered value. If
https://app.example.com/callbackis registered, an attacker might usehttps://app.example.com/callback.attacker.comorhttps://app.example.com/callback/../evil. - Wildcard registration. Some clients register
https://app.example.com/*. If the application has an open redirect at any path, the attacker routes the code through it. - Path traversal in URI. Test
https://app.example.com/callback%2F..%2F..%2Fevil— some validators normalize after checking. - Fragment injection. Test appending
#fragments to registered URIs. Some clients process the code from fragments.
Test redirect_uri bypass:
# Registered: https://app.example.com/callback
# Try these mutations:
https://app.example.com/callback@attacker.com
https://app.example.com/callback%2F%2Fattacker.com
https://app.example.com/callback/../../../attacker.com
https://app.example.com/callbackXattacker.com
https://attacker.com%2F@app.example.com/callback
State Parameter and CSRF
The state parameter protects against cross-site request forgery. The application generates a random value, includes it in the authorization request, and verifies that the value returned in the callback matches what was sent.
Without a valid state check, an attacker can craft a malicious authorization request, get a victim to complete authentication, and then inject the callback into the victim's browser — logging them into the attacker's account (account fixation) or hijacking the authorization code.
Test for:
- Missing state parameter — does the authorization request include
state? Is it verified in the callback? - Predictable state — is it a sequential number, timestamp, or other guessable value?
- State reuse — is a state value accepted more than once?
- State bypass — does removing the state parameter from the callback cause an error, or is it silently ignored?
# Test: remove state from callback and observe behavior
GET /callback?code=AUTH_CODE_HERE
# Expected: error - state mismatch
# Vulnerable: session created successfully
Nonce Bypass and ID Token Replay
The nonce is a value included in the authorization request that the IdP embeds in the issued ID token. The application verifies that the nonce in the ID token matches the one it originally sent. This prevents replay attacks — an attacker can't replay a captured ID token for a new session.
Nonce failures:
- Nonce not included in request. If the application doesn't send a nonce, the IdP doesn't embed one, and replay is possible.
- Nonce not verified. The application sends a nonce but doesn't verify it in the received ID token.
- Predictable nonce. An attacker who can predict the nonce value can pre-generate valid ID tokens at an IdP they control.
ID Token Validation Shortcuts
The ID token is a signed JWT. The OIDC specification defines exactly how it must be validated. Applications that skip validation steps are vulnerable to token forgery or substitution.
Required validations (and common omissions):
| Validation | What to check | Common failure |
|---|---|---|
| Signature | Verify using IdP's public key from JWKS endpoint | Skipped if using implicit flow or trusting token without verification |
Issuer (iss) |
Must exactly match the IdP's issuer URI | Missing check allows tokens from other IdPs |
Audience (aud) |
Must contain your client_id | Missing check allows tokens intended for other clients |
Expiry (exp) |
Must not be in the past | Expired tokens still accepted |
| Nonce | Must match the nonce sent in the request | Not checked — replay possible |
Algorithm (alg) |
Should be RS256 or ES256 — reject none |
Accepting alg: none allows unsigned tokens |
The alg: none vulnerability deserves special attention. The JWT specification allows tokens with no signature if the algorithm is set to none. Many early JWT libraries honored this. If the application doesn't explicitly reject alg: none, forge an unsigned ID token with any claims you want:
# Forged unsigned ID token (alg: none)
# Header: {"alg":"none","typ":"JWT"}
# Payload: {"sub":"admin@example.com","email":"admin@example.com","iss":"https://accounts.google.com","aud":"your-client-id","exp":9999999999,"nonce":"any"}
# Signature: (empty)
# Encoded:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbkBleGFtcGxlLmNvbSIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiJ5b3VyLWNsaWVudC1pZCIsImV4cCI6OTk5OTk5OTk5OSwibm9uY2UiOiJhbnkifQ.
Authorization Code Interception
The authorization code is short-lived and single-use, but if it can be intercepted before exchange, it grants full token access. Attack vectors:
- Referrer header leakage. If the callback page includes third-party resources (analytics, fonts), the authorization code in the URL may appear in the
Refererheader of those requests. - Browser history. The code appears in the URL and thus in browser history. Less relevant for server-side applications but matters for SPAs.
- Log injection. Web server access logs capture query parameters. Authorization codes in logs are a secrets management problem.
- PKCE missing (for public clients). Public clients (SPAs, mobile apps) can't keep a client secret. Without PKCE (Proof Key for Code Exchange), a stolen authorization code can be exchanged by anyone with the client_id. Verify PKCE is implemented for all public client flows.
Account Linking Vulnerabilities
Applications that allow users to link multiple social logins to one account are vulnerable to account takeover if the linking logic is flawed.
Common patterns:
- Email-based linking without verification. If the application links OIDC accounts based solely on the email claim, an attacker who controls an IdP (or exploits a misconfigured OIDC provider) can claim any email address and take over existing accounts.
- Forced account linking. If the application automatically links a new OIDC login to an existing account when emails match, and if the target IdP doesn't verify email ownership, account takeover is possible.
- Sub claim collisions. The
subclaim should be unique per IdP per user. Test whether the application correctly scopes subject identifiers to (issuer, subject) pairs — not just the subject alone.
Implicit Flow Risks
The OIDC implicit flow (response_type=id_token token) delivers tokens directly in the URL fragment rather than via a server-to-server token exchange. This was common for SPAs but is now considered legacy — the Authorization Code Flow with PKCE is the recommended replacement.
Implicit flow risks:
- Tokens in URL fragments end up in browser history, server logs (if the fragment is accidentally sent to the server), and Referer headers.
- No client authentication means tokens can be issued to any client claiming the right client_id.
- Phishing-friendly: an attacker constructs a valid-looking authorization URL with a modified redirect_uri.
If you see response_type=id_token or response_type=token in authorization requests, flag it as a finding — it should be migrated to the code flow with PKCE.
OIDC Discovery Endpoint Abuse
OIDC providers publish a discovery document at /.well-known/openid-configuration. This document tells clients where to find the authorization endpoint, token endpoint, JWKS URI, and supported claims.
If a relying party fetches this document dynamically based on a user-supplied issuer parameter (the "issuer mix-up attack" scenario), an attacker can point the application at a malicious IdP they control. The malicious IdP issues tokens that the application accepts as legitimate.
Test for:
- Does the application accept a user-controlled issuer URL for OIDC discovery?
- Does the application pin the expected issuer URI, or does it accept any issuer that passes signature validation?
- In multi-tenant applications, can a user from one tenant supply an issuer URI that resolves to a different tenant's IdP?
Testing Checklist
- Enumerate all authorization request parameters (state, nonce, redirect_uri, response_type, scope)
- Test redirect_uri mutations: prefix bypass, path traversal, fragment injection, open redirect chaining
- Verify state is present, unpredictable, and rejected if omitted or tampered with in the callback
- Verify nonce is present in requests, embedded in ID tokens, and validated on receipt
- Test ID token with
alg: none— should be rejected - Test ID token with modified claims (iss, aud, sub, email) — should fail signature validation
- Check for PKCE in all public client flows
- Test account linking with email from an attacker-controlled IdP
- Check if implicit flow is in use — flag for migration
- Test for issuer parameter injection in multi-IdP or multi-tenant setups
- Check for authorization code leakage via Referer headers
- Verify token endpoint requires correct client credentials (for confidential clients)
Tools
- Burp Suite — intercept and modify authorization requests and callbacks. The JWT Editor extension handles ID token manipulation.
- jwt.io — decode and inspect ID token claims and headers.
- oidcsecurity.com — automated OIDC security tests based on the OWASP OIDC Cheat Sheet.
- OAuth 2.0 Security BCP (RFC 9700) — the definitive reference for current best practices and known attack patterns.
- Ironimo — automated scanning detects common OIDC misconfigurations including insecure redirect URI patterns and missing security parameters.
Remediation
Use a maintained OIDC client library. Libraries like openid-client (Node.js), Authlib (Python), and spring-security-oauth2-client handle the validation logic correctly. Don't implement ID token validation manually.
Register exact redirect URIs. Never use wildcards or prefix matching. Register the exact URI including scheme, host, path, and any required parameters. Reject any redirect_uri that doesn't match exactly.
Always use and verify state and nonce. Generate cryptographically random values, store them in the session, and reject callbacks where these values don't match.
Explicitly reject alg: none. Configure your library to only accept the algorithm used by your IdP (typically RS256 or ES256). Reject tokens with unexpected algorithms.
Use PKCE for all public clients. SPAs and mobile apps must use the Authorization Code Flow with PKCE. There is no secure alternative for public clients.
Pin the issuer URI. Hard-code the expected issuer URI and reject tokens from any other issuer, regardless of signature validity.
OIDC vulnerabilities are high-impact but often detectable through automated scanning. Ironimo runs authenticated and unauthenticated checks against your OIDC flows — redirect URI validation, parameter handling, token acceptance — and surfaces misconfigurations before they reach production.
Start free scan