Implementing OAuth 2.0 PKCE for Public Clients
Learn how to add PKCE to OAuth 2.0 authorization code flow for SPAs and mobile apps, preventing authorization code interception with a per‑request verifier and challenge.
07 Aug 2025, 12:21 UTC

Why Public Clients Need PKCE
Public clients—such as single‑page applications (SPAs) and native mobile apps—cannot keep a client_secret confidential. If the secret is embedded in the app binary or JavaScript, an attacker can extract it and use the intercepted authorization code to obtain an access token. Proof Key for Code Exchange (PKCE, RFC 7636) replaces the static secret with a one‑time, cryptographically random value that only the legitimate client can reproduce, thereby mitigating authorization code interception attacks.
Prerequisites
- An OAuth 2.0 Authorization Server that implements RFC 7636 (most modern servers do).
- A registered
client_idfor your application. - A client‑side runtime capable of generating random strings, performing SHA‑256 hashing, and Base64URL encoding.
Step‑by‑Step Procedure
1. Generate a Code Verifier
The code_verifier is a high‑entropy random string. Per RFC 7636 it must be 43‑128 characters long and contain only the unreserved characters A‑Z a‑z 0‑9 - . _ ~. Example (illustrative only):
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Store this value temporarily (e.g., in sessionStorage or a secure, HttpOnly cookie) so it survives the redirect but is not exposed in URLs.
2. Derive the Code Challenge
Transform the verifier into a challenge using the S256 method (SHA‑256 followed by Base64URL encoding without padding).
- Compute SHA‑256 of the
code_verifierbytes. - Base64URL‑encode the hash, stripping any trailing
=characters.
For the example verifier above, the resulting challenge is:
E9MelS_S_pS_X_S_Example_Challenge
Note: the string shown is a placeholder; a real challenge will be exactly 43 characters of Base64URL.
3. Make the Authorization Request
Redirect the user to the authorization endpoint, including the challenge and its method.
GET https://auth.example.com/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback&n
scope=openid%20profile&n
code_challenge=E9MelS_S_pS_X_S_Example_Challenge&n
code_challenge_method=S256
The server stores the challenge and associates it with the pending authorization request.
4. Exchange the Authorization Code for Tokens
After the user authenticates and is redirected back to your redirect_uri with an code parameter, send a POST to the token endpoint, providing the original verifier.
POST https://auth.example.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&n
client_id=YOUR_CLIENT_ID&n
code=AUTHORIZATION_CODE_RECEIVED&n
redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback&n
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
The server hashes the received code_verifier with SHA‑256, Base64URL‑encodes the result, and compares it to the previously stored challenge. A match yields an access (and possibly refresh) token; a mismatch returns 400 Bad Request with error invalid_grant.
Validation and Verification Checklist
| Check | Expected Result | Failure Indication |
|---|---|---|
| Network trace – authorization request | code_challenge and code_challenge_method=S256 present in query string |
Missing parameters → server treats request as legacy flow (no PKCE protection) |
| Token request with incorrect verifier | Response: 400 Bad Request, error=invalid_grant |
Token issued despite mismatch → server not validating PKCE (critical flaw) |
| Code challenge format | Base64URL string, length 43, no +, /, or padding = |
Malformed challenge → server returns invalid_request |
Implementation Considerations
- Verifier storage: Keep the verifier only for the duration of the redirect.
sessionStorageis cleared when the page/tab closes; a secure, HttpOnly cookie with a short lifetime is also acceptable. Never place the verifier in the URL or in logs. - Method choice: Always use
S256. Theplainmethod is defined for legacy devices that cannot compute SHA‑256 and offers no cryptographic protection; avoid it unless you are forced to interoperate with such a device. - Entropy source: Use a cryptographically secure random number generator (e.g.,
window.crypto.getRandomValuesin browsers orSecureRandomon Android/iOS).
What PKCE Does Not Change
PKCE is a per‑request mechanism; it does not alter any state on the Authorization Server. Consequently, there is no server‑side configuration to “rollback.” If you discover a bug in your client‑side implementation, simply correct the code and redeploy. Reverting to the classic Authorization Code Flow would remove the interception protection for public clients, so it is not recommended as a recovery step.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.