OAuth Missing State Parameter: CSRF Login / Account Linking Attacks
state binds the OAuth dance to the browser that started it. Skip it (or ignore it), and an attacker can finish an authorization code flow in the victim’s browser using a code tied to the attacker’s IdP login. Outcomes range from “victim is logged into attacker’s app account” to “victim’s app account links to attacker’s Google.”
Two impacts people mix up
Login CSRF via OAuth: Victim ends up with an attacker-controlled session at the client app — similar story to password login CSRF, useful when the app stores victim-supplied data in that session.
Account linking / ATO: Victim is already logged into the client. Attacker starts OAuth with their own IdP user, captures the redirect with code, and tricks the victim’s browser into hitting /callback?code=.... The client attaches the attacker’s IdP identity to the victim’s session — or vice versa depending on implementation. That is often full account takeover.
Missing state enables the CSRF. Bad redirect_uri validation is a different bug. You can have either or both.
Test recipe
- Start OAuth at the client; drop or omit
stateif the client still redirects to the IdP. - Complete login at the IdP in the attacker browser; intercept the redirect to the client callback with a valid
code. - Deliver that callback URL to the victim browser (logged into the target account for linking tests).
- Observe session identity / linked social account changes.
If the client requires state but accepts any value without server-side lookup, that is still broken — replay an attacker-chosen state.
For OIDC, also check nonce on the ID token. Missing nonce invites ID token replay/mixups in parallel with state problems.
Nuances that change severity
Some apps create a brand-new user on every OAuth login and never link. Forced login still may matter. Others overwrite user.email from IdP claims without proving email ownership — chain material. PKCE fixes code interception on public clients; it does not replace state for CSRF.
Evidence to attach
Victim account before/after screenshots, the callback URL (redact secrets as required), and proof no unique state was validated. Cite PortSwigger’s OAuth lab material and RFC 6749 §10.12 on CSRF.
Fixes
Generate unguessable state, store it server-side (or signed cookie), validate on callback, delete after use. Use PKCE for public clients. For linking flows, require re-authentication and show clear account-merge confirmations.
Question worth asking in the report: “Can I attach my IdP user to someone else’s logged-in session with one crafted callback?” If yes, you are not filing a theoretical CSRF — you are filing takeover.