awesome-everything RU
↑ Back to the climb

Security

Authorization code flow with PKCE

Crux How OAuth 2.1''''s mandatory PKCE prevents code interception, why exact-match redirect URIs block open-redirect exfiltration, and what state and nonce defend against.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 10 min

An attacker monitors a mobile app’s outgoing requests and captures the one-time authorization code in the redirect URL. Without PKCE, they exchange the code for tokens and own the account. PKCE is the lock that makes a stolen code worthless.

How PKCE works — step by step

PKCE (Proof Key for Code Exchange, RFC 7636) adds a cryptographic challenge to the authorization code flow. OAuth 2.1 mandates it for every client, including confidential server-side apps.

Before the redirect:

  1. Client generates a code_verifier — a random 43–128 character URL-safe string (cryptographically random).
  2. Client computes code_challenge = base64url(SHA-256(code_verifier)).
  3. Client sends the code_challenge (but NOT the code_verifier) to the authorization server in the /authorize request.

After user consent: 4. Authorization server returns a one-time code in the redirect. It stores the code_challenge alongside the code.

Token exchange: 5. Client POSTs to /token with the code AND the original code_verifier. 6. Authorization server hashes the code_verifier, compares to the stored code_challenge. Match → tokens issued. No match → request rejected.

An attacker who intercepts the code in step 4 cannot redeem it — they need code_verifier, which was never sent over the wire.

PKCE code binding
1Client generates code_verifier (random 43–128 chars, stays local)
2Client computes code_challenge = base64url(SHA-256(verifier))
3/authorize request carries code_challenge (hash only, verifier stays secret)
4Auth server stores challenge → issues code in redirect URL
5/token request carries code + code_verifier — auth server hashes, compares, mints tokens
Attacker gets the code in step 4 but not the verifier → /token call rejected.

The exact-match redirect URI rule

OAuth 2.1 requires the redirect_uri in the token request to exactly match a pre-registered URI — no wildcards, no path prefixes, no partial matches.

Without this rule: an attacker can trigger a flow with redirect_uri=https://yourapp.com/comments?next=https://attacker.com. If your app has any open-redirect on /comments, the authorization code lands on the attacker’s server. The exact-match rule shuts this down: the URI must be character-for-character identical to a registered value.

For native apps where you cannot reliably register a URL (deep links, custom URI schemes), OAuth 2.1 + RFC 8252 require either http://127.0.0.1:RANDOM_PORT (loopback) or a private-use URI scheme registered with the OS.

State and nonce: two separate anti-attack tokens

State is OAuth’s anti-CSRF: the client generates a random value at /authorize time, the server echoes it back in the redirect, the client verifies it matches. Without state, an attacker can send a victim a crafted /authorize URL pointing to the attacker’s redirect — on click, the victim’s browser silently completes the flow under the attacker’s account.

Nonce is OIDC’s anti-replay: the client sends a random value in /authorize; the IdP embeds it inside the id_token. The client verifies the nonce in the token matches what it sent. Without nonce, an attacker who captures an id_token from another channel can replay it to a different session.

Both are random per-request. Both are validated server-side. Neither is optional in modern flows.

Quiz

Why does OAuth 2.1 mandate PKCE for ALL clients, including confidential server-side clients?

Quiz

A client validates the id_token signature and iss, but skips the nonce check. What attack is enabled?

Order the steps

Put the full authorization code + PKCE flow in order:

  1. 1 Client generates code_verifier and code_challenge (SHA-256 of verifier)
  2. 2 Client redirects browser to /authorize with code_challenge, state, nonce, client_id, redirect_uri
  3. 3 User authenticates at the authorization server and approves scopes
  4. 4 Authorization server redirects to redirect_uri with code and state
  5. 5 Client verifies state matches; POSTs to /token with code + code_verifier
  6. 6 Authorization server hashes verifier, compares to stored challenge, issues access + ID tokens
  7. 7 Client validates ID token (signature, iss, aud, exp, nonce) and reads user identity
Recall before you leave
  1. 01
    What does PKCE prevent, and how?
  2. 02
    Why does OAuth 2.1 require exact-match redirect URIs?
  3. 03
    What is the difference between the state parameter and the nonce parameter?
Recap

The authorization code flow with PKCE is the only grant type allowed for user-facing apps in OAuth 2.1. PKCE prevents code interception: the verifier never leaves the client, so a stolen code cannot be redeemed. Exact-match redirect URIs prevent open-redirect code exfiltration. The state and nonce parameters close two independent attack windows — CSRF on the flow and replay on the ID token. All three are mandatory in OAuth 2.1; skipping any one is a documented CVE vector.

Connected lessons
appears again in202
Continue the climb ↑ID token validation and JWKS cache management
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.