A fintech startup lost $2.3 million in a single weekend. The attacker didn't exploit a zero-day or run a phishing campaign. They manipulated a redirect URI in an OAuth flow that the developers assumed was "secure by default."

The developers had followed a tutorial. They'd implemented OAuth 2.0 "correctly." But they'd missed the security nuances that separate a working implementation from a secure one.

What OAuth 2.0 Actually Is

OAuth 2.0 is an authorization framework, not an authentication protocol. That distinction alone causes half the security problems I see.

When you click "Sign in with Google," you're using OAuth. Instead of giving every app your password, you grant them specific permissions through a trusted authorization server. The flow is simple: redirect to auth server, user approves, get an authorization code, exchange it for an access token.

Sounds straightforward. That's the problem.

The Flaws That Actually Get Exploited

1. Redirect URI Manipulation

That fintech startup? Their validation used startsWith() to check redirect URIs. An attacker registered https://app.example.com/callback.evil.com and the validation passed. The authorization code went straight to the attacker's domain.

The fix: Exact string matching only. No prefix matching, no wildcards, no regex.

2. Missing PKCE

The authorization code travels through the browser. If an attacker intercepts it - through a malicious browser extension, a compromised network, or a rogue mobile app - they can exchange it for an access token.

PKCE (Proof Key for Code Exchange) solves this. The client generates a random secret, sends a hash of it with the authorization request, then proves it knows the original secret during token exchange. Even if the code is stolen, it's useless without the secret.

PKCE is required for public clients in OAuth 2.1. If you're not using it, start today.

3. Token Leakage

Access tokens leak through places developers don't think about:

Leakage Vector Risk Level
localStorage High - accessible via XSS
URL parameters Critical - logged everywhere
Application logs High - seen by ops teams
Referrer headers Medium - sent to third parties

The fix: Store tokens in memory for SPAs, httpOnly cookies for traditional web apps. Pass tokens in Authorization headers, never in URLs. Sanitize your logs.

4. Scope Over-Permissioning

I reviewed an app that requested read:all write:all delete:all when it only needed read:profile. When that token leaked, the attacker could delete user data.

Request minimum scopes. Validate scopes server-side. Principle of least privilege isn't optional.

5. Missing CSRF Protection

Without the state parameter, an attacker can trick users into authorizing malicious applications. Generate a random state value, include it in the authorization request, validate it in the callback. Simple but constantly skipped.

6. Insecure Token Lifecycle

Short-lived access tokens (15-60 minutes) limit damage from theft. Refresh token rotation - issuing a new refresh token every time one is used and invalidating the old one - detects compromise. If an attacker uses a stolen refresh token, the legitimate user's next attempt fails, alerting you immediately.

The Mistakes I See Constantly

Using OAuth for authentication. OAuth tells you what a user can do, not who they are. If you need identity, use OpenID Connect on top of OAuth.

Trusting client-side validation. Never check scopes or permissions in the browser. Always enforce server-side.

Ignoring token revocation. Users need to revoke access. Your system needs to invalidate compromised tokens. Build revocation endpoints and check revocation status on every request.

Still using Implicit Flow. It's deprecated. Tokens end up in browser history, logs, and referrer headers. Switch to Authorization Code with PKCE.

The Bottom Line

The most common OAuth vulnerabilities aren't exotic attacks. They're basic implementation mistakes: trusting redirect URIs, skipping PKCE, storing tokens in localStorage, requesting excessive scopes, and forgetting the state parameter.

Start here:

  1. Audit your redirect URI validation (exact matching only)
  2. Implement PKCE for all public clients
  3. Move tokens out of localStorage
  4. Shorten your token expiration times
  5. Add state parameter validation
  6. Implement refresh token rotation
  7. Request minimum required scopes

OAuth 2.0 can be secure. But it requires attention to details that most developers skip. Don't be most developers.