OIDC Without Nonce: Login CSRF That Actually Lands
OpenID Connect added nonce so an ID token cannot be replayed into a victim's browser session from an attacker-started login. When the client never sends nonce, or never checks it against the ID token claim, you get a clean login CSRF primitive.
This is not "CSRF on a state-changing POST." It is "attacker authenticates as themselves, then forces the victim's browser to finish the callback."
What broken looks like
During the authorize redirect, look for state and nonce. state alone mitigates some CSRF on the code exchange if the app binds it tightly to the user agent cookie. Many apps generate state, store nothing meaningful, and never send nonce.
Decode the id_token (the payload is not secret). Is there a nonce claim? Does the client library verify it? You can often tell from behavior: complete login A in browser A, capture the callback URL with code + tokens, and replay that callback in browser B where you are logged out or logged in as B.
If browser B ends up with A's session, you have the bug. Pair it with a victim who is already logged into a sensitive account and you can forcibly swap them onto the attacker's identity—classic login CSRF used for fixation-style tricks, planted profiles, or linking attacker OAuth to victim state on the next step.
Separate the cases
Be precise in the writeup:
- Missing nonce + implicit/hybrid ID token in front channel: high confidence.
- Code flow with server-side exchange: still check whether the client verified nonce on the returned ID token; some stacks validate signatures and ignore nonce.
- State not bound to session cookie: overlapping CSRF class; report both if both fail.
Do not claim account takeover of arbitrary victims from login CSRF alone unless you show how the forced session produces a useful action—SSO into a victim tenant, accepting an invite, or attaching payment methods under the wrong identity.
SPA libraries deserve a hard look. A React app that completes OIDC in a popup and posts the result to window.opener without origin checks can combine with missing nonce into a messier bug. Stick to the nonce claim first; add postMessage issues only when you have a second concrete failure.
Proof with two accounts you own
Account Attacker completes OIDC to the callback URL. Deliver that URL to Account Victim's browser (self-XSS-free: phishing link to yourself is fine in a report). Show Victim's app session cookie now maps to Attacker subject sub. Attach IdP logs if available.
I like a short table in the report: browser, starting session, callback URL (redacted), resulting sub. Reviewers skim. Make the swap obvious.
Fix: generate a cryptographic nonce, store it server-side next to state, request it on the authorize URL, and require an exact match in the ID token before creating a session. Rotate session IDs on login. Treat OIDC callbacks as CSRF-critical endpoints.
If the IdP supports PKCE and the client is public, enable both PKCE and nonce. They solve different problems; shipping only one is how these bugs survive "we already did OAuth hardening" checklists.