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:
- Audit your redirect URI validation (exact matching only)
- Implement PKCE for all public clients
- Move tokens out of localStorage
- Shorten your token expiration times
- Add state parameter validation
- Implement refresh token rotation
- 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.