OAuth2 is correctly specified but routinely misconfigured in production. Redirect URI validation failures, missing state parameters, and implicit flow misuse are documented in major platforms every year. The spec gives implementers enough rope to hang themselves, and most do.
Analysis Briefing
- Topic: OAuth2 implementation vulnerabilities in production apps
- Analyst: Mike D (@MrComputerScience)
- Context: Born from an exchange with Claude Sonnet 4.6 that refused to stay shallow
- Source: Pithy Security
- Key Question: What OAuth2 misconfiguration is most likely sitting in your production app right now?
How Redirect URI Validation Failures Enable Account Takeover
OAuth2 authorization codes are delivered to a redirect URI specified in the authorization request. If the server does not validate the redirect URI strictly against the registered value, an attacker can substitute their own URI and capture the authorization code.
Common validation failures include prefix matching instead of exact matching, accepting redirect URIs that match only the domain but not the full path, and allowing open redirectors on the legitimate domain that forward the code to an attacker. A server that accepts any URI starting with https://yourapp.com/ will accept https://yourapp.com/logout?next=https://attacker.com/steal.
The fix is exact string matching on redirect URIs with no wildcards. Every permitted redirect URI should be explicitly registered. Subdomain wildcards and path prefix matching are both dangerous.
The State Parameter CSRF Bypass Nobody Implements Correctly
The OAuth2 state parameter is a CSRF defense. The client generates a random value, includes it in the authorization request, and verifies it matches when the authorization server redirects back with a code. Without state verification, an attacker can trick a victim into completing an OAuth flow that attaches the attacker’s account to the victim’s session.
Many implementations generate a state value but never verify it. Some use predictable state values like timestamps or user IDs. Both defeat the purpose entirely.
The correct implementation is a cryptographically random, single-use value stored in the session before the authorization request is sent and verified before the authorization code is exchanged. Libraries like Auth0’s SDK handle this correctly. Rolling your own state management is where the bugs appear.
When PKCE Actually Fixes the Authorization Code Interception Problem
Proof Key for Code Exchange (PKCE) was designed for mobile and single-page apps that cannot securely store client secrets. It also closes the authorization code interception attack that affects any OAuth2 flow running over a channel where codes can be intercepted.
PKCE generates a random code_verifier, hashes it to produce a code_challenge, and sends the challenge with the authorization request. The authorization server requires the original verifier during token exchange. An attacker who intercepts the code cannot exchange it without the verifier they never had.
PKCE is now recommended for all OAuth2 flows, not just public clients. The OAuth 2.1 draft makes it mandatory. If your implementation does not use PKCE in 2026, you are behind the current security baseline regardless of client type.
What This Means For You
- Enforce exact string matching on redirect URIs, because prefix and wildcard matching are the most commonly exploited OAuth misconfiguration in bug bounty reports.
- Verify the state parameter on every authorization callback, and use a cryptographically random single-use value stored server-side, not a predictable or client-side value.
- Enable PKCE for all OAuth2 flows regardless of client type, since the OAuth 2.1 draft makes it mandatory and it closes the code interception attack at no real cost.
- Audit your OAuth2 implementation against the OAuth Security BCP (RFC 9700), which documents every current attack pattern and the corresponding required mitigation.
Enjoyed this deep dive? Join my inner circle:
- Pithy Security → Stay ahead of cybersecurity threats.
