Authorization Code + PKCE flow
Click each block to inspect the exact data moving through the flow. The important security invariant: the client sends only the derived challenge up front, then proves knowledge of the original verifier at token exchange.
Live PKCE generator
Generate a valid verifier, derive the S256 challenge, and test how the authorization server verifies the token request.
Generate verifier and challenge
RFC 7636 defines the verifier as a high-entropy cryptographic random string using unreserved URI characters, 43 to 128 characters long. The S256 challenge is base64url(SHA256(ASCII(verifier))).
API shape
PKCE changes both the authorization request and token request. It does not replace redirect URI checks, client authentication for confidential clients, or state/nonce correlation.
Public clients prove possession using PKCE but cannot keep a client secret. Confidential clients still authenticate to the token endpoint, commonly with an Authorization Basic header, and should also use PKCE.
Verification lab
Paste a verifier into the simulated token request. The server recomputes the challenge and compares it with the challenge stored when the code was issued.
Attack simulator: authorization code interception
Walk through the exact scenario PKCE was designed for: the authorization code leaks through a redirect, log, malicious app link handler, browser extension, proxy, or referer-style bug.
Scenario controls
Run the flow with or without PKCE and observe whether the stolen authorization code can be exchanged for tokens.
Event log
What PKCE stops
- Authorization code interception before the token request.
- Malicious native app registering the same custom URL scheme.
- Code leakage through logs or front-channel redirects, if the verifier remains secret.
- Authorization-code injection against confidential clients when the injected code does not match the session-bound verifier.
What PKCE does not stop
- Access token theft after token issuance.
- Open redirect or weak redirect URI validation by itself.
- XSS that steals the verifier before token exchange.
- Bad account linking or missing ID token validation in OIDC login.
Security engineering takeaway
PKCE binds the authorization code to the client instance that started the flow. It is a code redemption control, not a full OAuth threat model by itself.
Training labs for engineering teams
Use these exercises to teach the implementation details that usually cause real PKCE mistakes in production systems.
1. The plain method trap
In plain mode, the value sent as code_challenge is the verifier itself. If an observer sees the authorization request and later obtains the authorization code, PKCE no longer adds a useful proof because the verifier-equivalent value was already exposed.
GET /authorize? response_type=code code_challenge=VERIFIER_VALUE code_challenge_method=plain # Training question: # What secret remains hidden from an observer?
2. Authorization Code Injection: confidential clients still need PKCE
A backend web application can authenticate to /token with a client secret, but that only proves which application is redeeming the code. It does not prove that this browser session initiated the authorization request that produced the code.
Attacker injects a stolen victim code into: https://client.example/callback?code=VICTIM_CODE&state=ATTACKER_STATE Backend has valid client_secret, so without PKCE: client redeems VICTIM_CODE at /token
3. PKCE protection boundary
PKCE protects authorization-code redemption. After tokens are issued, token theft, replay, and misuse require separate controls.
- Use least-privilege scopes and short access-token lifetimes.
- Rotate refresh tokens and detect reuse.
- Consider sender-constrained tokens such as DPoP or mTLS for high-risk APIs.
- Protect browser/native storage from XSS, malware, logs, and backups.
How to use this in internal training
Show an authorization request with code_challenge_method=plain. Ask: what secret remains hidden if this URL is logged?
Expected control: enforce S256 and reject plain except documented legacy exceptions.
Show a confidential-client callback containing a stolen victim code and an attacker session state. Ask: what does the client secret prove?
Expected control: bind the code to the session-owned verifier and reject token exchange when the proof does not match.
After successful token issuance, assume the access token is stolen from browser storage. Ask: does PKCE still help?
Expected control: use short lifetimes, refresh rotation, safe storage, and sender-constrained tokens such as DPoP or mTLS for high-risk APIs.
Facilitator exercise
Pick a lab and ask engineers to identify the vulnerable line, the failed assumption, and the correct platform control.
PKCE implementation checklist
Use this as a review checklist for OAuth clients and authorization servers.
Client requirements
Authorization server requirements
Precise terminology and edge cases
Security review notes to avoid common PKCE misunderstandings.
Cryptographic correctness checkpoints
Use URL-safe Base64: replace + with -, replace / with _, and remove = padding.
Generate the verifier with a CSPRNG such as crypto.getRandomValues. Do not use Math.random(), timestamps, counters, or reusable values.
The verifier must be 43 to 128 characters from the RFC 7636 unreserved character set. A raw 32-byte random value becomes 43 characters after base64url encoding.
PKCE terms
| Term | Meaning |
|---|---|
| code_verifier | High-entropy secret generated by the client. It must be kept local until the token request. |
| code_challenge | Derived value sent in the authorization request. With S256, it is not reversible back to the verifier. |
| code_challenge_method | Use S256. plain exists for compatibility but exposes a verifier-equivalent value in the front channel, so internal authorization servers should reject it unless a documented legacy exception exists. |
| Authorization code | Short-lived, single-use value issued after authorization. PKCE makes it useless without the verifier. |
Common misconceptions
| Misconception | Correction |
|---|---|
| PKCE replaces state. | No. PKCE proves possession of the verifier for code redemption. state still helps correlate responses, restore UI state, and provide defense-in-depth against CSRF-style mixups. |
| PKCE replaces client secrets. | No. Confidential clients should still authenticate to the token endpoint. PKCE is an additional binding of authorization code to client instance. |
| The challenge is a secret. | No. The challenge is sent through the front channel. The verifier is the sensitive value. |
| PKCE fixes OAuth redirect bugs. | No. Strict redirect URI validation, no open redirects, and secure callback pages are still required. |
Mini quiz
Test the key distinction.
Question: An attacker steals the authorization code from the callback URL, but the legitimate client kept the verifier secret. What happens at the token endpoint?