5.1 — The Top 12 OAuth/OIDC/JWT Attacks: Exploit Mechanics & Defense
Goal: Master the offensive mechanics of the 12 most common authentication attacks, audit your systems against each vector, and implement defense-in-depth mitigations before attackers exploit them in production.
Prerequisites: Stages 1 through 4 complete.
Table of Contents
- Attack 1: CSRF on the Authorization Callback
- Attack 2: Algorithm Confusion (RS256 vs HS256 &
alg=none) - Attack 3: The Confused Deputy Attack (Audience Hijacking)
- Attack 4: PKCE Downgrade & Code Interception
- Attack 5: IdP Mix-Up Attack (RFC 9207)
- Attack 6: Open Redirect via Lax Redirect URI Validation
- Attack 7: Token Leakage via Referer Headers & Logs
- Attack 8: Stolen Refresh Token Replay
- Attack 9: IDOR via Predictable or Mutable
sub - Attack 10: Scope Escalation & Privilege Creep
- Attack 11: JWT Header Injection (
kid/jkuPoisoning) - Attack 12: Session Fixation on the Auth Boundary
- The Master Security Audit Checklist
- Exercises & Verification
- Next Step
Attack 1: CSRF on the Authorization Callback
Root Cause
The client application fails to generate, store, and validate a cryptographically secure state parameter when initiating an authorization flow.
Exploitation Mechanism
- Attacker initiates an authorization code flow with the IdP for their own account.
- When the IdP redirects back to the attacker’s browser with
https://app.com/callback?code=ATTACKER_CODE, the attacker intercepts the request and stops it before the code is consumed. - Attacker crafts a phishing link or hidden image pointing to
https://app.com/callback?code=ATTACKER_CODEand tricks a victim into clicking it. - The victim’s browser sends the request with their own session cookies. The victim’s application backend redeems
ATTACKER_CODEand binds the victim’s session to the attacker’s identity. - When the victim enters credit card details or creates private documents, they are uploaded directly into the attacker’s account.
sequenceDiagram autonumber actor Attacker as Attacker actor Victim as Victim participant App as Target Application participant IdP as Identity Provider Attacker->>IdP: Starts OAuth Login IdP-->>Attacker: Callback?code=ATTACKER_CODE Note over Attacker: Captures code without redeeming Attacker->>Victim: Sends CSRF link to Victim Victim->>App: Submits /callback?code=ATTACKER_CODE App->>IdP: Exchanges ATTACKER_CODE for token Note over App: Logs victim in as ATTACKER!
Defense
Generate a cryptographically random state (at least 128 bits of entropy), store it in an HttpOnly, SameSite=Lax encrypted session cookie, and reject any callback where the returned state parameter does not match the cookie.
Attack 2: Algorithm Confusion (RS256 vs HS256 & alg=none)
Root Cause
A JWT verification library dynamically uses the algorithm specified in the unverified JWT header (alg) to decide which verification algorithm to execute.
Exploitation Mechanism
- The server signs tokens using asymmetric RS256 (RSA private key to sign, public key to verify).
- The attacker downloads the server’s public key (e.g. from
/.well-known/jwks.jsonor PEM file). - The attacker creates a forged token, sets
"alg": "HS256", and sets claims to{"sub": "admin"}. - The attacker signs the forged token using the server’s RSA public key string as the HMAC secret key!
- When the server verifies the token:
- It reads
"alg": "HS256". - It calls its verification function:
verify(token, public_key). - The HMAC library treats the public key string as a shared secret! Since both the attacker and the server used the public key string as the HMAC secret, the signature check succeeds!
- It reads
Defense
Never trust the alg header from the token. Explicitly whitelist allowed algorithms in your verification options:
# SECURE: Explicit algorithm whitelist enforced
jwt.decode(token, public_key, algorithms=["RS256"])Attack 3: The Confused Deputy Attack (Audience Hijacking)
Root Cause
Resource Server (RS) A accepts a valid JWT without verifying that the aud (Audience) claim explicitly names RS A.
Exploitation Mechanism
- Client requests a token for a low-security service (e.g.
aud: https://chat.company.com). - The user passes this valid token to the high-security billing API (
https://billing.company.com/transfer). - If the billing API only checks signature validity and expiration, it accepts the token and performs the transfer, even though the token was never authorized for the billing service.
Defense
Always enforce strict audience verification:
jwt.decode(token, key, algorithms=["RS256"], audience="https://billing.company.com")Attack 4: PKCE Downgrade & Code Interception
Root Cause
An Authorization Server supports PKCE for public clients, but allows requests without code_challenge for backwards compatibility.
Exploitation Mechanism
- A malicious native application on mobile registers the same custom URI scheme as the legitimate app (
com.bank.app://oauth). - When the user authorizes the legitimate app, the attacker’s app intercepts the redirect and captures the
code. - If the AS does not require PKCE, the attacker redeems the code directly at
/token.
Defense
Enforce PKCE globally across the Authorization Server. If an authorization request omits code_challenge or uses insecure code_challenge_method=plain, reject with invalid_request.
Attack 5: IdP Mix-Up Attack (RFC 9207)
Root Cause
A client supports multiple Identity Providers (e.g., Google and a Malicious IdP), but uses the same redirect URI callback for both without binding the callback to the specific IdP.
Exploitation Mechanism
- User clicks “Log in with Google”.
- Attacker intercepts and modifies the client request to redirect the user to
evil-idp.com. - Evil IdP authenticates the user and redirects back to the client’s callback with an authorization code.
- The client, believing this code came from Google, sends the code along with its Google
client_secretto Google’s token endpoint, leaking credentials or codes.
Defense
Implement RFC 9207: The Authorization Server must return an iss parameter in the authorization response (/callback?code=...&iss=https://accounts.google.com), and the client must verify that iss matches the IdP that was originally contacted.
Attack 6: Open Redirect via Lax Redirect URI Validation
Root Cause
The AS allows wildcard subdomains, partial paths, or regex matches for redirect_uri (e.g., https://example.com/*).
Exploitation Mechanism
Attacker targets an open redirect vulnerability on the host:
https://auth.example.com/authorize?client_id=123
&redirect_uri=https://example.com/logout?redirect_to=https://evil.com
The AS validates that redirect_uri starts with https://example.com/, redirects to the logout endpoint with the authorization code, and the logout endpoint bounces the code straight to evil.com.
Defense
Enforce OAuth 2.1 strict exact string matching: No wildcards, no path traversal, byte-for-byte exact equality.
Attack 7: Token Leakage via Referer Headers & Logs
Root Cause
Access tokens passed in URL query parameters (?access_token=...) or stored in browser localStorage.
Exploitation Mechanism
- Client makes request:
GET /dashboard?access_token=secret_token_123. - The dashboard loads a third-party script, stylesheet, or external link (
<a href="https://analytics.com">). - The browser automatically sends the full URL, including
?access_token=..., in the HTTPRefererheader to the third party. - Token is logged in reverse proxy logs, browser history, and web analytics databases.
Defense
- Ban tokens in query parameters (OAuth 2.1 mandate).
- Send tokens exclusively in
Authorization: Bearerheaders. - Set
Referrer-Policy: strict-origin-when-cross-originon all responses.
Attack 8: Stolen Refresh Token Replay
Root Cause
Refresh tokens are long-lived, static strings issued without rotation or sender-constraining.
Exploitation Mechanism
- Attacker steals a refresh token from local storage, memory, or backup database.
- Attacker uses the refresh token to continuously generate fresh access tokens for months, undetected by the legitimate user or security operations.
Defense
Implement Refresh Token Rotation with Automatic Reuse Detection:
- Every refresh exchange issues a new refresh token and burns the previous one.
- If an old token is presented a second time, all tokens in that family are immediately revoked.
Attack 9: IDOR via Predictable or Mutable sub
Root Cause
Using sequential integers (1001, 1002) or mutable usernames for the sub claim.
Exploitation Mechanism
- Attacker notes their token has
"sub": "1002". - Attacker finds an API accepting
X-User-Idor modifies internal parameters to probe1001. - If an IdP recycles a deleted username (
john), a new user inheriting that username gains access to the previous user’s orphaned documents.
Defense
Use globally unique, random, immutable UUIDv4/UUIDv7 identifiers for sub.
Attack 10: Scope Escalation & Privilege Creep
Root Cause
The Resource Server checks whether a token exists, but fails to check if the token includes the specific scope required for the endpoint.
Exploitation Mechanism
Client requests scope read:profile. The API endpoint /api/admin/delete-database checks if request.user: but does not verify scope: admin. The request succeeds.
Defense
Enforce fine-grained scope authorization at the API route handler or policy agent:
if "admin:delete" not in token.get("scope", "").split():
raise HTTPException(status_code=403, detail="Insufficient token scope")Attack 11: JWT Header Injection (kid / jku Poisoning)
Root Cause
The verification server reads the kid (Key ID) or jku (JWKS URL) header parameter from an untrusted JWT and uses it directly in SQL queries, filesystem lookups, or remote HTTP requests without sanitization.
Exploitation Mechanism
- SQL Injection via
kid: Server runsSELECT key FROM keys WHERE id = '+header.kid+'. Attacker sets"kid": "xxxx' UNION SELECT 'my_hmac_key' --". - Directory Traversal via
kid: Server runsopen("/etc/keys/" + header.kid). Attacker sets"kid": "../../dev/null", causing the server to verify against an empty key string. jkuSSRF: Attacker sets"jku": "https://attacker.com/jwks.json". Server fetches the attacker’s public keys and validates the attacker’s forged signature.
Defense
- Whitelist known
kidvalues or match strictly against cached local JWKS sets. - Never construct SQL queries or file paths using
kid. - Never fetch remote
jkuURLs specified dynamically in incoming tokens.
Attack 12: Session Fixation on the Auth Boundary
Root Cause
The application preserves an unauthenticated pre-login session ID after the user completes authentication.
Exploitation Mechanism
- Attacker visits
https://app.com, obtains a guest session cookiesession_id=attacker_session. - Attacker sends a link to the victim:
https://app.com/login?session=attacker_session. - Victim logs in successfully. The server associates
attacker_sessionwith the victim’s account. - Attacker now uses
session_id=attacker_sessionin their own browser and has full authenticated access.
Defense
Always regenerate session IDs upon login and logout:
# Upon successful authentication:
request.session.regenerate() # Destroys old session ID, issues fresh cryptographic IDThe Master Security Audit Checklist
| Check # | Vulnerability | Verification Method | Status |
|---|---|---|---|
| 01 | Callback CSRF | Verify state is verified on /callback | [ ] |
| 02 | Alg Confusion | Verify algorithms=["RS256"] explicitly set | [ ] |
| 03 | Confused Deputy | Verify aud claim matches Resource Server ID | [ ] |
| 04 | PKCE Downgrade | Verify AS rejects missing code_challenge | [ ] |
| 05 | Mix-up Attack | Verify RFC 9207 iss check on callback | [ ] |
| 06 | Open Redirect | Verify exact string matching on redirect_uri | [ ] |
| 07 | Query Leakage | Verify no ?access_token= in URLs or logs | [ ] |
| 08 | Refresh Theft | Verify Refresh Token Rotation + Reuse Detection | [ ] |
| 09 | Predictable Sub | Verify sub is UUID/opaque and immutable | [ ] |
| 10 | Scope Escalation | Verify route-level scope enforcement | [ ] |
| 11 | Header Poisoning | Verify kid is sanitized and jku ignored | [ ] |
| 12 | Session Fixation | Verify session ID regenerated on login | [ ] |
Exercises & Verification
- Simulate Alg Confusion: In a lab environment, take an RS256 token, re-encode it with HS256 using the server’s public key as secret. Confirm your server’s verification logic rejects it.
- Execute State Tampering: Modify the
statequery parameter on an OAuth callback and verify that authentication immediately aborts with an HTTP 400/403. - Audit Token Transport: Inspect your access logs across your reverse proxies (nginx/ALB) and ensure zero access tokens or client secrets appear in URLs.
Next Step
Understanding attacks is only half the battle. Next, we examine where tokens must and must not live: Token Storage & Side-Channel Leaks.
→ Stage 5.2 — Token Storage & Side-Channel Leaks: Browser, Mobile, and BFF Architectures