Many engineering teams view OAuth 2.0 as a plug-and-play solution. They import a library, configure the `client_id`, and assume the Identity Provider handles all security. This is fundamentally false. The client application bears the primary responsibility for validating cryptographic signatures, matching session states, and securely storing the resulting tokens. During a penetration test, we target the integration layer—the handoff between the IdP and your backend.
The Authorization Code Flow: Redirect URI Validation Bypasses
When a user initiates an OAuth flow, the client redirects them to the authorization server, specifying where to send the user back via the redirect_uri parameter. After successful authentication, the server redirects the victim back to this URI, appending the highly sensitive authorization code. If the server does not enforce exact, strict string matching on the redirect URI, attackers exploit this validation gap to steal codes.
// VULNERABLE redirect parameter using wildcard or regex matching
https://auth.simpalabs.com/oauth/authorize?
client_id=123&
redirect_uri=https://example.com/oauth/callback&
response_type=code
// Attacker-tampered bypass payload exploiting path traversal
https://auth.simpalabs.com/oauth/authorize?
client_id=123&
redirect_uri=https://example.com/oauth/callback/../../attacker-site.com/leak&
response_type=code If the IdP accepts the tampered URI, the victim logs in normally, but their browser is subsequently redirected to `attacker-site.com/leak?code=[SECRET_CODE]`. The attacker instantly exchanges the code for an access token and takes over the victim's account. The Fix: Enforce strict, exact-match string validation on the authorization server for all registered redirect URIs. Never allow wildcard (`*`) subdomain matching, dynamic path traversal resolution, or unvalidated URI parameters.
Bypassing PKCE with Downgrade Attacks
Proof Key for Code Exchange (PKCE) mitigates authorization code interception, particularly for mobile applications or Single Page Applications (SPAs) that cannot securely hold a `client_secret`. The client sends a `code_challenge` (a SHA-256 hash of a random verifier) in the initial request, and presents the raw verifier during the code exchange.
However, the specification technically allows a `code_challenge_method` of `plain`. If the developer uses `plain`, the challenge is literally just the raw verifier sent in plaintext over the initial redirect URL. If we can intercept that initial URL (via referer headers, proxy logs, or shoulder surfing), we instantly possess the verifier required to steal the access token. We actively test your authorization server to ensure it explicitly rejects `code_challenge_method=plain` and mandates `S256` hashing.
Cross-Site Request Forgery (CSRF) via Missing State Parameter
The state parameter is a cryptographically secure random token generated by the client and sent alongside the authorization request. The authorization server returns this exact state value in the callback. The client application must explicitly verify that the returned state matches the value stored in the user's active session cookie before it executes the token exchange.
If the client fails to validate the state parameter, the flow is completely vulnerable to an OAuth CSRF attack. An attacker initiates their own login flow, intercepts the resulting authorization code (or implicit token) bound to their own identity, and tricks a victim into visiting the callback URL containing the attacker's code. The client application processes the callback and binds the victim's local browser session to the attacker's account. If the victim then uploads a confidential document or enters payment details, they are unknowingly saving it directly into the attacker's account.
Scope Escalation and Dynamic Registration
We audit the granularity of your OAuth scopes. If a third-party application requests the `profile:read` scope, does your backend strictly limit the issued access token to only read operations? Attackers frequently test for Scope Escalation by modifying the initial authorization request to include highly privileged scopes (like `admin:write` or `billing:full`). If the authorization server fails to validate the requested scopes against the client's pre-approved whitelist, it will blindly issue an escalated token, granting the attacker full infrastructure control.
Auditing OpenID Connect (OIDC) ID Token Validation
OpenID Connect introduces ID Tokens, which are standard JSON Web Tokens (JWTs) representing the user's authenticated identity. When your client receives the ID Token from the endpoint, it must aggressively validate the token before trusting the identity. Common implementation mistakes we exploit during audits include:
- Missing Signature Check: The client reads the JSON payload directly (often using `jwt.decode` instead of `jwt.verify`) without cryptographically validating the signature against the provider's JSON Web Key Set (JWKS) public keys. We bypass this by simply forging our own token.
- Audience (aud) Validation Bypass: The client fails to verify that the `aud` claim in the token explicitly matches the client's registered `client_id`. This allows a token issued for a completely different application to be replayed against yours (the Confused Deputy problem).
- Algorithm Confusion: The client trusts the `alg` header inside the token. We change the algorithm from RS256 (asymmetric) to HS256 (symmetric) and sign the token using the IdP's public key as the secret.
- Expiration (exp) Omission: The client ignores the expiration timestamp, allowing attackers to hoard and replay old, revoked tokens indefinitely.
Token Storage and Exfiltration via XSS
If your Single Page Application (React, Angular, Vue) stores the retrieved OAuth access tokens or OIDC ID tokens in `localStorage` or `sessionStorage`, you are highly vulnerable. Any successful Cross-Site Scripting (XSS) payload on your domain can execute `console.log(localStorage.getItem('access_token'))` and exfiltrate the token to an external server. We strongly advise architecting your application to use the Backend-for-Frontend (BFF) pattern, where tokens remain securely on the backend, and the frontend relies strictly on `HttpOnly`, `Secure`, `SameSite=Strict` session cookies.
Wildcard redirect URI allows authorization code theft
During an audit of a financial aggregator, the authorization server accepted any redirect URI that merely started with the registered domain. We identified an open redirect vulnerability on the marketing site (`marketing.com/out?url=...`). We crafted an OAuth login link setting the `redirect_uri` to the open redirect, pointing to our server. When the victim logged in, the authorization code was forwarded directly to our infrastructure, allowing us to generate an access token and drain the linked accounts. You must register exact redirect URIs and outright reject wildcard hosts, path prefixes, user-info sections, and encoded path changes.
Integrating OAuth 2.0 or OIDC providers? Schedule a deep-dive protocol security audit.
Book an OAuth / OIDC AuditFrequently asked questions
Why is open redirect URI validation in OAuth 2.0 dangerous?
If the authorization server allows wildcard redirect URIs, an attacker can craft a login link containing a redirect parameter pointing to a malicious site. After the victim authenticates, the authorization code or access token is forwarded directly to the attacker's server, leading to immediate account takeover.
How does PKCE prevent authorization code interception?
Proof Key for Code Exchange (PKCE) requires the client to generate a secret verifier and send its hash (the challenge) with the initial authorization request. During the final code exchange, the client must present the raw verifier. This ensures that an attacker who intercepts the authorization code cannot exchange it for an access token, because they do not possess the raw verifier.
What is the security difference between OAuth 2.0 and OpenID Connect?
OAuth 2.0 is an authorization framework strictly designed for granting API access tokens. It does not authenticate the user. OpenID Connect is an identity layer built on top of OAuth 2.0 that introduces ID Tokens (signed JSON Web Tokens) to cryptographically verify the user's profile identity and authentication timestamp.
Related reading
Blog: JWT token security mistakes · API data leaks · Auth0 & Clerk audits
Services: API security testing · Secure architecture review