OAuth 2.0 PKCE Explained for JavaScript SPAs
OAuth 2.0 Proof Key for Code Exchange (PKCE) is a security extension designed to protect authorization flows on public clients, such as JavaScript Single-Page Applications (SPAs) and mobile apps. This article explains what PKCE is, why the traditional OAuth flows are insufficient for browser-based applications, and the exact step-by-step mechanism PKCE uses to prevent authorization code interception attacks.
The Vulnerability of Public Clients
In OAuth 2.0, applications are categorized as either confidential or
public clients. Confidential clients (like traditional server-rendered
web apps) can securely store sensitive credentials like a
client_secret on a backend server.
JavaScript Single-Page Applications run entirely within the user’s browser, making them public clients. Any secret embedded in JavaScript source code or browser storage is easily accessible to end-users and malicious actors.
Historically, SPAs used the OAuth 2.0 Implicit Flow, which returned access tokens directly in the URL fragment to bypass the need for a client secret. However, the Implicit Flow is now deprecated because it exposes tokens to access log leaks, browser history inspection, and cross-site scripting (XSS) extraction.
What is PKCE?
PKCE (defined in RFC 7636 and pronounced “pixy”) enhances the
standard Authorization Code Flow so that public clients
can securely exchange an authorization code for access tokens without
needing a static client_secret.
Instead of relying on a pre-shared secret, PKCE uses a dynamically generated secret created on-the-fly for every individual authorization request.
How PKCE Works in a JavaScript SPA
The PKCE flow replaces static secrets with a cryptographic challenge-response mechanism using two primary components:
- Code Verifier: A cryptographically random string generated by the SPA with high entropy (between 43 and 128 characters).
- Code Challenge: A transformed version of the Code Verifier, created by hashing the Code Verifier using the SHA-256 algorithm and then Base64URL-encoding the result.
The Step-by-Step Authorization Process
- Generation: When a user initiates a login, the SPA
generates a unique
code_verifierlocally and computes the correspondingcode_challenge. - Authorization Request: The SPA redirects the user
to the Authorization Server’s
/authorizeendpoint, including thecode_challengeand thecode_challenge_method=S256in the query parameters. - User Authentication: The Authorization Server
authenticates the user, stores the
code_challenge, and redirects the user back to the SPA with anauthorization_code. - Token Request: The SPA sends a
POSTrequest to the token endpoint (/oauth/token). This request includes the receivedauthorization_codealong with the original, unhashedcode_verifier. - Validation and Issuance: The Authorization Server
applies the SHA-256 hash to the provided
code_verifierand compares the output to thecode_challengeit stored in step 3. - Token Response: If the hashes match, the Authorization Server confirms that the entity requesting the token is the exact same application instance that initiated the login, and it issues the access and refresh tokens.
How PKCE Secures JavaScript Applications
PKCE neutralizes authorization code interception attacks. Even if a malicious actor or extension intercepts the authorization code returned via the browser redirect, the stolen code is completely useless on its own.
To exchange the code for tokens, an attacker must provide the
original code_verifier. Because the
code_verifier is stored in the initiating application’s
memory and is never transmitted during the initial authorization
redirect, an external attacker cannot reproduce it, keeping the
JavaScript SPA authentication flow secure.