OAuth Security Testing
A comprehensive guide for testing OAuth 2.0 and OpenID Connect implementations for security vulnerabilities that could lead to account takeover.
Quick Start
When testing an OAuth implementation, follow this checklist:
- Map the OAuth flow - Identify all endpoints, parameters, and redirect URIs
- Test redirect_uri validation - Check for open redirects and weak validation
- Verify state parameter handling - Ensure CSRF protection is properly implemented
- Check token handling - Look for token leakage in URLs, storage, or logs
- Test client secret protection - Verify secrets aren't exposed in client-side code
- Validate authorization code lifecycle - Test for replay, lifetime, and binding issues
- Review consent screen security - Check for clickjacking and prompt bypass
OAuth 2.0 Fundamentals
Key Components
| Component | Description |
|---|---|
| Resource Owner | The user whose data is being accessed |
| Resource Server | Server managing authenticated requests (e.g., socialmedia.com) |
| Client Application | Application seeking authorization (e.g., example.com) |
| Authorization Server | Server issuing access tokens after authentication |
| client_id | Public identifier for the application |
| client_secret | Confidential key (must never be exposed to users) |
| redirect_uri | URL for post-authorization redirect (must be pre-registered) |
| state | CSRF protection token (must be cryptographically random) |
| code | Authorization code exchanged for access token |
| access_token | Token used for API requests on behalf of user |
| refresh_token | Token to obtain new access tokens without re-prompting |
Standard Authorization Code Flow
1. User → Client: Clicks "Login with [Provider]"
2. Client → Auth Server: GET /auth?response_type=code&client_id=XXX&redirect_uri=YYY&scope=ZZZ&state=ABC
3. Auth Server → User: Consent screen
4. User → Auth Server: Approves
5. Auth Server → Client: GET /callback?code=DEF&state=ABC
6. Client → Auth Server: POST /token with code, client_id, client_secret
7. Auth Server → Client: Returns access_token, refresh_token
8. Client → Resource Server: API calls with access_token
Vulnerability Testing
1. Open redirect_uri
What to test: The authorization server must redirect only to pre-registered, exact redirect URIs.
Attack patterns:
# No validation - any URL accepted
https://idp.example/auth?redirect_uri=https://attacker.tld/callback
# Weak substring checks - bypass with lookalikes
https://idp.example/auth?redirect_uri=https://evilmatch.com
https://idp.example/auth?redirect_uri=https://match.com.evil.com
https://idp.example/auth?redirect_uri=https://match.com.mx
https://idp.example/auth?redirect_uri=https://evil.com#match.com
https://idp.example/auth?redirect_uri=https://match.com@evil.com
# IDN homograph attacks
https://idp.example/auth?redirect_uri=https://xn--example.com (redirects to Unicode domain)
# Path traversal on allowed hosts
https://idp.example/auth?redirect_uri=https://example.com/oauth/../anything
# Wildcard subdomain abuse
https://idp.example/auth?redirect_uri=https://attacker.example.com/callback
# Non-HTTPS callbacks
https://idp.example/auth?redirect_uri=http://example.com/callback
Testing steps:
- Identify the registered redirect_uri in the OAuth flow
- Try replacing the domain with attacker-controlled domains
- Test substring bypasses if validation exists
- Check for path traversal on allowed hosts
- Verify HTTPS enforcement
- Review auxiliary parameters:
client_uri,policy_uri,tos_uri,initiate_login_uri - Check
/.well-known/openid-configurationfor additional endpoints
2. Redirect Token Leakage on Allowlisted Domains
What to test: Even "trusted" domains can leak tokens if they have attacker-controlled paths.
Attack pattern:
1. Start legitimate OAuth flow to get pre-token
2. Craft authorization URL with allowlisted domain but attacker-controlled path:
https://apps.facebook.com/<attacker_app>
3. After approval, IdP redirects with tokens in URL
4. JavaScript on attacker page reads window.location and exfiltrates
5. Replay captured tokens against privileged endpoints
Testing steps:
- Identify allowlisted redirect domains
- Check if they support user-controlled paths (legacy apps, user namespaces, CMS)
- Create a test page on the allowlisted domain
- Set it as redirect_uri and verify tokens appear in URL
- Attempt to exfiltrate via JavaScript
- Test replay against downstream endpoints
3. XSS in Redirect Implementation
What to test: Redirect URLs reflected in responses may be vulnerable to XSS.
Test payload:
https://app.victim.com/login?redirectUrl=https://app.victim.com/dashboard</script><h1>test</h1>
Testing steps:
- Find redirect parameters in login/auth flows
- Inject XSS payloads into redirect URLs
- Check if reflected without encoding
- Test in various contexts: query params, fragments, headers
4. CSRF - State Parameter Issues
What to test: The state parameter must be cryptographically random, per-session, and validated.
Checklist:
-
stateparameter present in authorization request -
stateis cryptographically random (not predictable) -
stateis validated on callback (same value returned) -
stateis tied to user session (cookie, local storage) -
statecannot be user-supplied (fixation prevention) - Missing
statestill works? (opt-in defense) - Tampered
stateaccepted? (validation bypass)
Attack pattern:
1. Attacker authenticates with their account
2. Captures final redirect with ?code=XXX&state=YYY
3. Drops request, keeps URL
4. Forces victim browser to load URL (link, iframe, form)
5. If state not validated, victim account links to attacker's IdP profile
Testing steps:
- Remove
statefrom authorization request - does it still work? - Tamper with
statein response - is it rejected? - Check if
stateis predictable (redirect paths, JSON blobs without entropy) - Test state fixation - can you supply your own
statevalue? - Verify
stateis bound to session (different users, different sessions)
5. Pre-Account Takeover
What to test: Account creation without email verification can enable takeover.
Attack patterns:
Pre-registration:
- Attacker creates account with victim's email
- Victim later uses OAuth login
- App links OAuth account to attacker's pre-created account
Lax OAuth email verification:
- Attacker registers with OAuth provider
- Changes email to victim's address
- Victim's OAuth login links to attacker's account
Testing steps:
- Create account with victim's email (no verification)
- Attempt OAuth login with same email
- Check if accounts merge or link
- Test OAuth provider email change flows
6. Client Secret Disclosure
What to test: client_secret must never be recoverable by end users.
Where to look:
- Mobile APKs/IPAs (unpack and grep)
- Desktop installers
- Electron apps
- Single-page applications
- Bundled config files (plist, JSON, XML)
- Decompiled strings
Testing steps:
# Unpack and search for secrets
unzip app.apk -d app/
grep -r "client_secret" app/
grep -r "oauth" app/
# Check for Base64 encoded secrets
strings app.apk | base64 -d 2>/dev/null | grep -i "secret\|token\|key"
# Review config files
cat app/Info.plist
cat app/config.json
If secret is found:
- Capture any victim authorization
code(via redirect_uri bug, logs, etc.) - Exchange code for token independently:
POST /oauth/access_token Content-Type: application/x-www-form-urlencoded client_id=XXX&client_secret=YYY&code=ZZZ&grant_type=authorization_code - Verify PKCE is not required (public clients should use PKCE, not secrets)
7. Client Secret Brute Force
What to test: Weak client secrets can be brute-forced.
Test request:
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: target.com
code=77515&redirect_uri=http://target.com/callback&grant_type=authorization_code&client_id=public_client_id&client_secret=[bruteforce]
Testing steps:
- Capture a valid authorization code
- Use Burp Intruder to brute-force
client_secret - Look for different response codes/sizes indicating success
- Test common weak secrets:
secret,password,123456, client_id itself
8. Referer/Header/Location Artifacts
What to test: OAuth codes and tokens leaked via HTTP headers or browser APIs.
Attack patterns:
Classic Referer leak:
- OAuth redirect includes
?code=&state=in URL - Any navigation sends Referer header to CDNs/analytics/ads
- Third parties receive OAuth artifacts
- OAuth redirect includes
Telemetry confused deputy:
- SDKs react to
postMessageevents - Send
location.href/referrerto backend APIs - Attacker injects token into flow
- Later reads SDK API logs to recover OAuth artifacts
- SDKs react to
Testing steps:
- Check if
code/stateappear in URL after OAuth redirect - Monitor Referer headers sent to third parties
- Check analytics/telemetry SDKs for URL logging
- Test
postMessagerelay attacks - Review server logs for OAuth parameter exposure
9. Access Token in Browser History
What to test: Access tokens should never reach the browser in Authorization Code flow.
Check for:
- Tokens in URL query/fragment
- Tokens in browser history
- Tokens in server logs
- Tokens in JavaScript state (React/Vue stores, global variables)
- Tokens in Web Storage (localStorage/sessionStorage)
- Tokens over HTTP (not HTTPS)
- Tokens through debugging/corporate proxies
Impact: Any XSS, Referer leak, or proxy logging becomes instant account compromise.
10. Authorization Code Lifecycle
What to test: Codes must be short-lived, single-use, and replay-aware.
Testing steps:
Lifetime test:
- Capture authorization code
- Wait 5-10 minutes
- Attempt to redeem - should fail
Sequential reuse test:
- Redeem code once (success)
- Redeem same code again - should fail
Concurrent redemption test:
- Fire two token requests in parallel
- Only one should succeed
Replay handling test:
- Attempt reuse after successful redemption
- Verify any minted tokens are revoked
11. Token Binding to Client
What to test: Authorization codes must be bound to the issuing client.
Testing steps:
- Capture
codefor App A - Send to App B's token endpoint
- If token is returned, audience binding is broken
- Test first-party token minting endpoints
- Check if arbitrary
state/app_idaccepted - Verify nonce/redirect URI validation
12. Response Mode Variations
What to test: Different response modes may have different security properties.
response_mode=query -> ?code=2397rf3gu93f (in URL, visible in history/logs)
response_mode=fragment -> #code=2397rf3gu93f (not sent to server, but in browser)
response_mode=form_post -> POST form with code (more secure)
response_mode=web_message -> postMessage (requires careful implementation)
Testing steps:
- Try each response_mode value
- Check if server accepts all modes
- Verify security implications of each mode
- Test for mode confusion attacks
13. Prompt Parameter Bypass
What to test: prompt=none may bypass user consent.
Test:
https://idp.example/auth?response_type=code&client_id=XXX&redirect_uri=YYY&prompt=none
Testing steps:
- Add
prompt=noneto authorization request - Check if consent screen is skipped
- Verify this is only allowed for trusted flows
- Test with
prompt=login,prompt=consent,prompt=select_account
14. Clickjacking Consent Dialogs
What to test: OAuth consent/login dialogs should not be frameable.
Testing steps:
- Load IdP authorization URL in iframe:
<iframe sandbox="allow-forms allow-scripts allow-same-origin" src="https://idp.example/auth?..."></iframe> - Check for
X-Frame-Options: DENY/SAMEORIGIN - Check for
Content-Security-Policy: frame-ancestors 'none' - If frameable, create clickjacking PoC with overlaid buttons
- Use NCC Group's clickjacking PoC generator
15. OAuth ROPC Flow - 2FA Bypass
What to test: Resource Owner Password Credentials flow may bypass 2FA.
What to look for:
- OAuth endpoints accepting username/password directly
- Tokens returned with full user permissions
- No 2FA challenge in the flow
Testing steps:
- Find ROPC endpoint (usually
/tokenwithgrant_type=password) - Test with username/password
- Check if 2FA is required
- Verify token permissions
16. AWS Cognito Abuse
What to test: Cognito tokens may have excessive permissions.
Test commands:
# Read user info
aws cognito-idp get-user --region us-east-1 --access-token <token>
# Change email (if permitted)
aws cognito-idp update-user-attributes \
--region us-east-1 \
--access-token <token> \
--user-attributes Name=email,Value=attacker@example.com
Testing steps:
- Obtain Cognito access token
- Check token permissions
- Attempt to modify user attributes
- Test email change for account takeover
17. Abusing Other Apps' Tokens
What to test: Apps expecting tokens (not codes) may not validate token ownership.
Attack pattern:
- Attacker creates OAuth app and logs in with victim's OAuth provider
- Attacker gets victim's OAuth token for their app
- Attacker uses this token to login to victim's target app
- If target app doesn't validate token's client_id, account is compromised
Testing steps:
- Create test OAuth application
- Login with OAuth provider
- Capture the access token
- Try using token on other apps expecting OAuth tokens
- Check if client_id validation exists
18. Dynamic Client Registration SSRF
What to test: Client registration endpoints may enable SSRF.
Vulnerable parameters:
logo_uri- Logo URL fetched by serverjwks_uri- JWK document URLsector_identifier_uri- Redirect URI array URLrequest_uris- Request URI list
Test request:
POST /register
Content-Type: application/json
{
"client_name": "Test App",
"redirect_uris": ["https://example.com/callback"],
"logo_uri": "http://internal-server/admin",
"jwks_uri": "http://169.254.169.254/latest/meta-data/"
}
Testing steps:
- Find client registration endpoint (usually
/register) - Test with internal URLs in URI parameters
- Check for SSRF indicators (different response, timeout, internal IP access)
- Test with metadata service URLs
19. OAuth Discovery URL Abuse (RCE)
What to test: Desktop clients forwarding IdP metadata to OS may enable RCE.
Attack pattern:
- Host malicious OAuth server
- Return dangerous
authorization_endpointin discovery:{ "authorization_endpoint": "file:///c:/windows/system32/calc.exe", "token_endpoint": "https://evil/idp/token" } - Client calls OS URL handler with malicious URI
- Code executes under user context
Test payloads:
// Windows
"authorization_endpoint": "file:///c:/windows/system32/calc.exe"
"authorization_endpoint": "cmd://powershell -enc <base64>"
// macOS/Linux
"authorization_endpoint": "file:///Applications/Calculator.app/Contents/MacOS/Calculator"
"authorization_endpoint": "xdg-open /etc/passwd"
// Custom schemes
"authorization_endpoint": "ms-excel:"
"authorization_endpoint": "data:text/html,<script>alert(1)</script>"
Testing steps:
- Target OAuth-capable desktop/agent clients
- Intercept or host discovery response
- Replace endpoints with dangerous schemes
- Observe if client validates scheme/host
- Test cross-platform variations
20. Mutable Claims Attack
What to test: Relying on mutable claims (email) instead of immutable sub can enable takeover.
Attack pattern:
- Attacker creates Azure AD organization
- Uses Microsoft login with attacker's account
- Changes email to victim's email in Entra ID
- Target app relying on email field links accounts
Testing steps:
- Check what claims the app uses for user identification
- Verify if
sub(immutable) or email (mutable) is used - Test with OAuth providers allowing email changes
- Attempt account linking via email change
21. Client Confusion Attack
What to test: Apps not validating token's client_id can be confused.
Attack pattern:
- Attacker creates public OAuth app
- Users login, attacker harvests access tokens
- Attacker reuses tokens on vulnerable app
- If app doesn't validate client_id, accounts are compromised
Testing steps:
- Create test OAuth application
- Login and capture access token
- Try token on other apps
- Check if client_id is validated in token
22. Scope Upgrade Attack
What to test: Authorization servers trusting scope parameter in token request.
Attack pattern:
- Get authorization code with limited scope
- Request token with elevated scope parameter
- If server trusts scope parameter, token has higher privileges
Testing steps:
- Capture authorization code
- Modify scope in token request
- Check if returned token has elevated permissions
- Verify JWT signature and scope validation
23. Redirect Scheme Hijacking (Mobile)
What to test: Custom URI schemes on mobile can be hijacked.
Attack pattern:
- Multiple apps can register same URI scheme
- Attacker app registers
com.example.app:// - OAuth redirect goes to attacker app
- Attacker captures authorization code
Testing steps:
- Identify custom URI schemes used
- Check if scheme-only or scheme+path
- Test with malicious app registering same scheme
- Verify intent filter specificity on Android
Testing Workflow
Phase 1: Reconnaissance
- Identify OAuth endpoints (
/auth,/token,/callback,/register) - Map all redirect URIs used
- Document OAuth parameters and their values
- Check for OpenID Connect discovery (
/.well-known/openid-configuration) - Identify OAuth providers in use
Phase 2: Vulnerability Testing
- Test redirect_uri validation (open redirect)
- Verify state parameter handling (CSRF)
- Check token storage and transmission
- Test authorization code lifecycle
- Verify client secret protection
- Test consent screen security
- Check for additional attack vectors
Phase 3: Exploitation
- Attempt account takeover with identified vulnerabilities
- Test token replay and reuse
- Verify impact of each finding
- Document exploitation steps
Phase 4: Reporting
- Document each vulnerability with:
- Description
- Impact
- Steps to reproduce
- Proof of concept
- Remediation recommendations
Remediation Guidelines
For Developers
redirect_uri validation:
- Use exact match against pre-registered URIs
- Validate scheme (HTTPS only)
- Normalize URLs before comparison
- Reject wildcard subdomains
State parameter:
- Generate cryptographically random value per session
- Store in secure, HttpOnly cookie
- Validate on callback (exact match)
- Reject if missing or mismatched
Token handling:
- Never expose access tokens to browser in Authorization Code flow
- Use PKCE for public clients
- Store tokens securely (not in localStorage)
- Implement token rotation
Client secrets:
- Never embed in client-side code
- Use PKCE for mobile/desktop apps
- Rotate secrets regularly
- Monitor for leaked secrets
Authorization codes:
- Short lifetime (5-10 minutes)
- Single-use only
- Bind to client_id and redirect_uri
- Revoke on reuse attempt
For Security Teams
- Regularly audit OAuth implementations
- Monitor for leaked client secrets
- Test third-party OAuth integrations
- Implement OAuth security monitoring
- Review consent screen configurations
References
- OAuth 2.0 Documentation
- RFC 6749 - OAuth 2.0 Specification
- RFC 7636 - PKCE
- PortSwigger OAuth Research
- Doyensec OAuth Vulnerabilities
- NCC Group Authorization Code Guide
- Amla Labs OAuth RCE
Tools
- Burp Suite - Intercept and modify OAuth flows
- OAuth Tester - Automated OAuth vulnerability scanning
- Postman - Test OAuth endpoints
- NCC Group Clickjacking PoC - Test consent screen framing
- Custom scripts - See bundled scripts for common tests