OAuth 2.0 Vulnerabilities Explained: PKCE, State, Redirect URI and Token Security

SCRs Team
March 28, 2026
Updated May 8, 2026
16 min read
521 words
Share

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.

VulnerabilityImpactPrevalence
Open Redirect in redirect_uriAccount hijackingVery common
CSRF (missing state parameter)Account linking attacksCommon
Authorization code interceptionFull account takeoverModerate
Token leakage via Referer headerSession hijackingCommon
PKCE bypassMobile app account takeoverIncreasing
Scope escalationPrivilege elevationModerate

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.

Editorial standards

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.

Named author: SCRs Team
Published: Mar 28, 2026
Updated: May 8, 2026

Questions or corrections?

Review our editorial standards, learn more about the company, or contact us if a page needs clarification.

AI Security Audit

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.

Manual review for agent, prompt, and retrieval attack paths
Actionable remediation guidance for your AI stack
Coverage for LLM apps, MCP integrations, and internal AI tools

Talk to SecureCodeReviews

Get a scoped review path fast

Manual review
Actionable fixes
Fast turnaround
Security-focused

Advertisement