Stopping Code Interception: Implementing OAuth 2.0 with PKCE for Public Clients
Stop authorization code injection in SPAs and mobile apps. Learn how OAuth 2.0 PKCE replaces static secrets with dynamic cryptographic challenges to secure public clients.
11 Sept 2026, 20:14 UTC

The Vulnerability of Public Clients
In a standard OAuth 2.0 Authorization Code flow, a client exchanges an authorization code for an access token. For "confidential clients" (like a secure backend server), this is safe because the server can provide a client secret that the attacker doesn't have. However, "public clients"—such as Single Page Applications (SPAs) or mobile apps—cannot keep a secret. If an attacker intercepts the authorization code via a custom URI scheme or browser history, they can potentially exchange that code for a token, hijacking the user session.
The solution is Proof Key for Code Exchange (PKCE). Instead of a static secret, PKCE uses a dynamically generated, one-time cryptographic bridge to ensure that the entity requesting the token is the same entity that initiated the authorization.
How PKCE Changes the Handshake
PKCE (pronounced "pixie") introduces two new variables to the flow: the code_verifier and the code_challenge.
- Code Verifier: A high-entropy cryptographic random string generated by the client for every single request.
- Code Challenge: A transformed version of the verifier. Usually, this is a SHA-256 hash of the verifier, which is then Base64URL encoded.
The core logic is simple: the client tells the server what the result of the hash will be at the start of the flow, but only reveals the original string at the end. If the server hashes the provided verifier and it matches the initial challenge, the request is authenticated.
Implementation Walkthrough
Assume a client environment using JavaScript (browser) and an OAuth 2.0 provider supporting the S256 challenge method. This example follows the OAuth 2.0 (RFC 6749) and PKCE (RFC 7636) standards.
Step 1: Generate the Verifier and Challenge
The client generates a random string and hashes it. This happens locally before the user is redirected to the login page.
// Client-side logic (Conceptual JavaScript)
const codeVerifier = generateRandomString(64);
const codeChallenge = await sha256(codeVerifier);
const base64Challenge = base64UrlEncode(codeChallenge);
Step 2: The Authorization Request
The client redirects the user to the /authorize endpoint. Note the inclusion of the challenge and the method used.
// GET request to Authorization Server
/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
scope=openid profile&
code_challenge=base64Challenge_value&
code_challenge_method=S256
Step 3: The Token Exchange
After the user authenticates, the server sends a code back to the redirect URI. The client now sends that code along with the original, unhashed verifier to the /token endpoint.
// POST request to Token Endpoint
/token?
grant_type=authorization_code&
client_id=YOUR_CLIENT_ID&
code=RECEIVED_AUTH_CODE&
redirect_uri=YOUR_REDIRECT_URI&
code_verifier=original_codeVerifier_value
Verification and Diagnostics
To verify that PKCE is functioning correctly, use your browser's network inspector (F12) during the authentication flow:
- Check the /authorize call: Ensure
code_challengeandcode_challenge_method=S256are present in the query parameters. - Check the /token call: Ensure the
code_verifieris sent in the request body. - Failure Test: If you capture the
codefrom the URL and attempt to exchange it via a tool like cURL without providing the correctcode_verifier, the server must return a400 Bad Requestorinvalid_granterror.
Trade-offs and Limitations
While PKCE solves the code interception problem, it does not solve token storage risks. A common mistake is implementing PKCE but then storing the resulting access token in localStorage, where it is vulnerable to Cross-Site Scripting (XSS) attacks. For maximum security, use HttpOnly and Secure cookies to store tokens, keeping them inaccessible to client-side scripts.
Additionally, while S256 is the industry standard, some legacy providers only support plain (where the challenge equals the verifier). Avoid plain whenever possible, as it provides significantly less protection against certain types of interception.
Closing Decision
If you are building a mobile app or a frontend SPA, the Implicit Flow (which returns tokens directly in the URL) is deprecated. The modern standard is Authorization Code Flow + PKCE. It removes the need for a client secret on the frontend while providing a cryptographic guarantee that the token is delivered to the rightful requester.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.