CMD Guide
HomeSystem DesignAuthentication

hard OAuth 2.0 Security — PKCE, state, id_token

OAuth 2.0 Security — PKCE, state, and id_token validation

The OAuth authorization-code flow is only as safe as three checks that a "happy-path" trace usually omits, and each defends a specific, well-known attack: state stops login-CSRF / injected callbacks, PKCE binds the authorization code to the client that started the flow (the fix for public clients that can't hold a secret), and id_token claim validation stops token substitution and replay. Signature-verified is not the same as valid.

1. state — CSRF protection on the callback

Before redirecting to the authorization server, the client generates a random state, stores it (session/cookie), and sends it in the /authorize request. The server echoes it back on the callback. The client must compare the returned state to the one it sent and drop the request if they differ. Without it, an attacker can trick a victim's browser into completing an OAuth callback the attacker initiated (login-CSRF: the victim ends up logged into the attacker's account, or an injected code is swapped in). state ties the callback to the browser session that began the flow.

2. PKCE — bind the code to its originator (public clients)

A confidential client authenticates the token exchange with a client secret. A public client (SPA, mobile app) cannot keep a secret — it ships in the bundle. PKCE (Proof Key for Code Exchange) replaces the secret with a per-flow proof:

PKCE is now recommended for all clients, but it is mandatory in practice for public clients — the case a confidential-secret trace leaves uncovered.

OAuth authorization-code + PKCE sequence: client generates code_verifier/challenge, state, nonce; sends challenge+state+nonce to /authorize; verifies returned state; exchanges code+verifier; server checks SHA256(verifier)==challenge; returns tokens; client validates id_token claims aud/iss/exp/nonce plus signature.
OAuth authorization-code + PKCE sequence: client generates code_verifier/challenge, state, nonce; sends challenge+state+nonce to /authorize; verifies returned state; exchanges code+verifier; server checks SHA256(verifier)==challenge; returns tokens; client validates id_token claims aud/iss/exp/nonce plus signature.

3. id_token validation — claims, not just the signature

A valid signature only proves the issuer minted the token; it does not prove the token is for you, now, and for this login. Validate every claim:

Judgment layer — why / why-not / alternatives / trade-off

Pitfalls

Takeaways

Code sketch: PKCE generator

import base64, hashlib, secrets

verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=")
challenge = base64.urlsafe_b64encode(
    hashlib.sha256(verifier).digest()
).rstrip(b"=")

# /authorize?code_challenge=challenge&code_challenge_method=S256&state=...
# /token    body includes code_verifier=verifier

Related pages

Browser SPA hardening: Backend-for-Frontend (BFF)

For browser-based SPAs, the strongest modern pattern is often a Backend-for-Frontend (BFF): the browser completes the login through a small same-origin backend, and the BFF stores access and refresh tokens server-side instead of exposing them to JavaScript. The browser receives only an application session cookie, typically HttpOnly, Secure, and SameSite=Lax or SameSite=Strict. HttpOnly means an XSS payload cannot read the token value with document.cookie; SameSite limits cross-site cookie sending, reducing CSRF exposure; and the BFF can attach bearer tokens to upstream API calls from the server where injected browser script cannot steal them.

This does not make XSS harmless: injected script can still issue actions as the user while the page is open. It changes the failure mode from token theft and replay from anywhere until expiry to same-origin session abuse that must run through your BFF controls (CSRF checks, origin checks, step-up, rate limits, and audit logging). It is the right default when the SPA talks to sensitive APIs and you can afford a small backend tier.

OAuth 2.1 direction

The OAuth 2.1 draft folds the security BCP direction into the core model: new clients should use authorization code with PKCE, the implicit grant is deprecated, and PKCE is mandatory for public clients and expected broadly even for confidential clients. In practice: do not build a new SPA around implicit flow; use auth-code + PKCE, and consider a BFF when browser token theft is the main risk.


Re-authored and deepened for this guide, per RFC 6749/7636 (PKCE), OpenID Connect Core (id_token claim validation), and the OAuth 2.0 Security BCP. Re-authored/Deepened for this guide.

🤖 Don't fully get this? Learn it with Claude

Stuck on OAuth 2.0 Security — PKCE, state, id_token? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **OAuth 2.0 Security — PKCE, state, id_token** (System Design) and want to truly understand it. Explain OAuth 2.0 Security — PKCE, state, id_token from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **OAuth 2.0 Security — PKCE, state, id_token** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **OAuth 2.0 Security — PKCE, state, id_token** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **OAuth 2.0 Security — PKCE, state, id_token** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes