OAuth 2.0 Vulnerabilities Explained: PKCE, State, Redirect URI and Token Security
On this page
OAuth 2.0 Security Best Practices and Common Vulnerabilities
Teams searching for OAuth 2.0 security best practices usually need answers to three questions: how to stop redirect URI abuse, how to implement PKCE correctly, and how to prevent account-linking or token leakage attacks. Those are still the most common ways OAuth deployments fail in production.
OAuth 2.0 powers authentication for billions of users — Google, GitHub, Facebook, Microsoft — yet implementation errors remain one of the most common web vulnerabilities.
The problem isn't the spec. It's that OAuth has dozens of configuration options, and getting even one wrong opens a critical vulnerability.
| Vulnerability | Impact | Prevalence |
|---|---|---|
| Open Redirect in redirect_uri | Account hijacking | Very common |
| CSRF (missing state parameter) | Account linking attacks | Common |
| Authorization code interception | Full account takeover | Moderate |
| Token leakage via Referer header | Session hijacking | Common |
| PKCE bypass | Mobile app account takeover | Increasing |
| Scope escalation | Privilege elevation | Moderate |
Attack #1: Open Redirect via redirect_uri Manipulation
The most common OAuth vulnerability. If the OAuth provider doesn't strictly validate the redirect_uri, an attacker can steal authorization codes.
The Attack Flow
1. Attacker crafts malicious URL:
https://oauth.provider.com/authorize?
client_id=legit_app&
redirect_uri=https://legit-app.com.evil.com/callback&
response_type=code&
state=random
2. User clicks link, authenticates with provider
3. Provider redirects to attacker's domain with the auth code:
https://legit-app.com.evil.com/callback?code=AUTH_CODE_HERE
4. Attacker exchanges code for access token
5. Attacker now has access to user's account
Defense: Exact redirect_uri Matching
// ✅ Server-side: Validate redirect_uri EXACTLY
const ALLOWED_REDIRECTS = [
'https://myapp.com/auth/callback',
'https://staging.myapp.com/auth/callback',
];
function validateRedirectUri(uri: string): boolean {
return ALLOWED_REDIRECTS.includes(uri);
// ❌ Never use: uri.startsWith('https://myapp.com')
// ❌ Never use: uri.includes('myapp.com')
// ❌ Never use: regex matching on domain
}
Attack #2: CSRF — Missing State Parameter
Without a state parameter, an attacker can link their own OAuth account to a victim's session.
The Attack
1. Attacker starts OAuth flow, gets authorization code
2. Attacker crafts URL:
https://victim-app.com/callback?code=ATTACKERS_CODE
3. Victim visits the link (via phishing, embedded image, etc.)
4. Victim's account is now linked to attacker's Google account
5. Attacker can now log in to victim's account via "Login with Google"
Defense: Cryptographic State Parameter
import crypto from 'crypto';
// Generating the authorization URL
function buildAuthUrl(session: Session): string {
// ✅ Generate cryptographic random state
const state = crypto.randomBytes(32).toString('hex');
// Store state in session (or signed cookie)
session.oauthState = state;
return \`https://oauth.provider.com/authorize?${new URLSearchParams({
client_id: process.env.OAUTH_CLIENT_ID!,
redirect_uri: 'https://myapp.com/auth/callback',
response_type: 'code',
state: state,
scope: 'openid profile email',
})}\`;
}
// Handling the callback
function handleCallback(req: Request, session: Session) {
const { code, state } = req.query;
// ✅ Verify state matches
if (!state || state !== session.oauthState) {
throw new Error('CSRF detected: state mismatch');
}
// Clear state after use
delete session.oauthState;
// Exchange code for token...
}
Attack #3: Authorization Code Interception (Mobile Apps)
On mobile devices, custom URL schemes (e.g., myapp://callback) can be registered by any app — including malicious ones.
Defense: PKCE (Proof Key for Code Exchange)
import crypto from 'crypto';
// Step 1: Generate code verifier and challenge
function generatePKCE() {
const verifier = crypto.randomBytes(32)
.toString('base64url');
const challenge = crypto
.createHash('sha256')
.update(verifier)
.digest('base64url');
return { verifier, challenge };
}
// Step 2: Include challenge in authorization request
const { verifier, challenge } = generatePKCE();
// Store verifier securely
const authUrl = \`https://oauth.provider.com/authorize?${new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: 'code',
code_challenge: challenge,
code_challenge_method: 'S256',
})}\`;
// Step 3: Include verifier in token exchange
const tokenResponse = await fetch('https://oauth.provider.com/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
code_verifier: verifier, // ✅ Proves we initiated the flow
}),
});
PKCE is now required for ALL OAuth clients — not just mobile apps (OAuth 2.1 draft).
Attack #4: Token Leakage via Referer Header
If tokens are in the URL (implicit flow), they leak via the Referer header when users click any external link.
1. OAuth redirects to: https://myapp.com/callback#access_token=SECRET_TOKEN
2. Page loads, user clicks a link to https://external-blog.com
3. External site receives Referer header:
Referer: https://myapp.com/callback#access_token=SECRET_TOKEN
4. External site now has the user's access token
Defense: Never Use Implicit Flow
❌ response_type=token (Implicit flow — tokens in URL)
✅ response_type=code (Authorization Code flow — codes in URL, tokens server-side)
Attack #5: Scope Escalation
1. App requests: scope=read_profile
2. Attacker modifies request: scope=read_profile admin delete_users
3. If provider doesn't validate against registered scopes,
the token gets elevated permissions
Defense: Validate Scopes Server-Side
const REGISTERED_SCOPES = ['openid', 'profile', 'email'];
function validateScopes(requestedScopes: string[]): string[] {
// ✅ Only allow scopes that are pre-registered
return requestedScopes.filter(s => REGISTERED_SCOPES.includes(s));
}
Production OAuth Checklist
- Exact redirect_uri matching (no wildcards, no regex)
- Cryptographic state parameter on every request
- PKCE enabled for all clients (public and confidential)
- Authorization Code flow only (never Implicit)
- Token storage in HttpOnly Secure cookies (not localStorage)
- Short-lived access tokens (15 min) with refresh rotation
- Scope validation against registered app permissions
- Token revocation on logout
- Rate limiting on token endpoint
- Monitor for token reuse and code replay attacks
OAuth 2.0 FAQ
What are the most common OAuth 2.0 vulnerabilities?
The most common issues are redirect URI validation bugs, missing or weak state parameters, broken PKCE implementations, token leakage, scope escalation, and insecure token storage in browsers or mobile clients.
Is PKCE required for OAuth 2.0 now?
Yes, in practice it should be treated as mandatory for modern OAuth deployments, including mobile apps, SPAs, and increasingly confidential clients. It closes off authorization code interception attacks that are otherwise hard to detect.
What is the safest OAuth 2.0 flow for web apps?
For most modern applications, the safest default is the Authorization Code flow with PKCE, strict redirect URI matching, a cryptographically random state parameter, short-lived access tokens, and secure server-side token handling.
Published by SecureCodeReviews
This article is part of our original AI security and cybersecurity content library. We show publish and update dates, keep company and policy pages public, and update important guidance when material changes affect readers.
Planning an AI feature launch or security review?
We assess prompt injection paths, data leakage, tool use, access control, and unsafe AI workflows before they become production problems.
Advertisement
Free Security Tools
Try our tools now
Expert Services
Get professional help
OWASP Top 10
Learn the top risks
Related Articles
Threat Modeling for Developers: STRIDE, PASTA & DREAD with Practical Examples
Threat modeling is the most cost-effective security activity — finding design flaws before writing code. This guide covers STRIDE, PASTA, and DREAD methodologies with real-world examples for web, API, and cloud applications.
Building a Security Champions Program: Scaling Security Across Dev Teams
Security teams can't review every line of code. Security Champions embed security expertise in every development team. This guide covers program design, champion selection, training, metrics, and sustaining engagement.
The Ultimate Secure Code Review Checklist for 2025
A comprehensive, language-agnostic checklist for secure code reviews. Use this as your team's standard for catching vulnerabilities before they reach production.