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:

  1. 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, and nonce.
  2. User authenticates. The IdP authenticates the user and obtains consent if needed.
  3. Authorization code. The IdP redirects back to the application's redirect_uri with an authorization code and the state parameter.
  4. 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.
  5. 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 like sub and email.

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:

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:

# 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:

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:

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:

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:

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:

Testing Checklist

Tools

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
← Back to blog