Walk through the OAuth2 authorization code flow with PKCE, step by step
Public clients like SPAs and mobile apps cannot keep a secret, so PKCE binds the flow's start to its finish instead.
LEARN BY DOINGPut your intuition to work3 steps
Walk through the OAuth2 authorization code flow with PKCE, step by step
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#Why PKCE exists
The OAuth2 authorization code flow was originally designed assuming a confidential client — a server-side application that can hold a client_secret safely, away from end users. Single-page apps, mobile apps, and native desktop apps cannot make that assumption: anything shipped to the client can be extracted from it, so a hardcoded secret provides no real protection. Worse, on native platforms the redirect back to the app after login typically goes through a custom URI scheme (myapp://callback) or a universal link, and more than one app on the same device can sometimes register the same scheme — opening a window for a malicious app to intercept the authorization code meant for the legitimate one.
PKCE — Proof Key for Code Exchange, RFC 7636, pronounced "pixy" — closes that gap without requiring any shared secret at all. It binds the request that starts the flow to the request that finishes it, so that even if an attacker intercepts the authorization code in transit, they cannot exchange it for a token.
#The moving parts
- code_verifier — a high-entropy random string the client generates itself, kept only in memory or session storage, and never transmitted until the very last step.
- code_challenge —
BASE64URL(SHA256(code_verifier)), sent up front in the authorization request. Because SHA-256 is one-way, possessing the challenge does not let anyone recover the verifier. - code_challenge_method —
S256in every modern implementation; theplainmethod (sending the verifier itself as the challenge) exists for legacy compatibility only and provides no real protection.
#Step by step
- The client generates a
code_verifierand derivescode_challenge = BASE64URL(SHA256(code_verifier)). - The client redirects the user's browser to the authorization server's
/authorizeendpoint, includingclient_id,redirect_uri,response_type=code,scope, a randomstatevalue,code_challenge, andcode_challenge_method=S256. - The authorization server authenticates the resource owner — a login prompt, possibly MFA — and asks for consent to the requested scopes.
- On approval, the authorization server redirects back to
redirect_uriwith a short-livedcodeand the originalstateechoed back. - The client checks that the returned
statematches what it sent in step 2 (this is the CSRF defense — it is a separate concern from PKCE), then makes a direct, back-channel POST to the/tokenendpoint withgrant_type=authorization_code,code,redirect_uri,client_id, and — critically — the rawcode_verifier. - The authorization server recomputes
SHA256(code_verifier)and compares it against thecode_challengeit stored when it issued the code. A mismatch means the party exchanging the code is not the party that started the flow, and the request is rejected. - On a match, the authorization server issues an
access_token(and typically arefresh_token, plus anid_tokenif this is OpenID Connect). - The client calls the resource server with
Authorization: Bearer <access_token>. - The resource server validates the token — checking the signature and claims if it is a JWT, or calling an introspection endpoint if it is opaque — and serves the protected resource.
#Why interception still fails
| Flow | Client type | Secret required | Status |
|---|---|---|---|
| Authorization code (classic) | Confidential (server-side) | client_secret | Still valid for confidential clients |
| Authorization code + PKCE | Public (SPA, mobile, native) | None — code_verifier instead | Recommended for all clients |
| Implicit | Public, historically | None | Deprecated — avoid in new work |
🧠 Check your understanding
0/3 · 0/3 answered1. What problem does PKCE solve that the plain OAuth2 authorization code flow does not, for public clients like SPAs and mobile apps?
2. In the PKCE flow, when is the code_verifier itself sent to the authorization server?
3. What does the state parameter protect against that PKCE does not?