Hardening OAuth 2.0 Redirect Flows
Most OAuth vulnerabilities are not cryptographic. They are redirect handling, missing state, and flows that should have been retired years ago.
Published by Xevry
OAuth 2.0 is a framework with many optional parts, and most real-world vulnerabilities come from how those parts are wired together rather than from the protocol itself. The current guidance is consolidated in the OAuth 2.0 Security Best Current Practice, published as RFC 9700. This article covers the issues we see most often during application assessments.
1. Redirect URIs must match exactly
The authorization server sends the authorization code to the redirect_uri. If it accepts loosely matched URIs — prefix matching, wildcard subdomains, or ignored paths — an attacker can direct codes to a location they control.
- Register full redirect URIs and compare them with exact string matching.
- Avoid wildcard subdomains: a single subdomain takeover becomes an account takeover.
- Watch for open redirects on allowed hosts.
https://app.example.com/logout?next=…can bounce a code to an attacker even when the registered URI is exact.
2. Use PKCE for every client
Proof Key for Code Exchange (PKCE) binds the authorization request to the token request. The client generates a random code_verifier, sends its SHA-256 hash as the code_challenge, and must present the original verifier to redeem the code. A stolen code is useless without it.
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));const challenge = base64url( new Uint8Array(await crypto.subtle.digest("SHA-256", encode(verifier)))); authorizeUrl.searchParams.set("code_challenge", challenge);authorizeUrl.searchParams.set("code_challenge_method", "S256");PKCE was originally designed for mobile and single-page apps, but current best practice recommends it for confidential server-side clients too. Always use S256; the plain method offers no protection if the challenge is observed.
3. Bind the flow to the browser with state
Without a state parameter tied to the user's session, an attacker can complete an OAuth flow with their own account and trick a victim's browser into finishing it — logging the victim into the attacker's account, or linking the attacker's identity to the victim's profile. Generate an unguessable state per request, store it server-side or in a secure cookie, and reject callbacks that do not match. (PKCE also mitigates this when enforced by the authorization server, but state remains useful for carrying application context safely.)
4. Retire the implicit flow
The implicit grant returns access tokens directly in the URL fragment, where they can leak through browser history, referrer headers, and injected scripts. Current best practice says not to use it. Single-page apps should use the authorization code flow with PKCE.
5. Validate everything you receive
- For OpenID Connect, validate the ID token's signature,
iss,aud,exp, andnonce. - Never decide the signature algorithm from the token header. Pin the expected algorithm and key set.
- Request the minimum scopes you need, and verify the scopes actually granted.
- Prefer sender-constrained tokens (mTLS or DPoP) for high-value APIs, so a leaked token cannot be replayed from elsewhere.