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:

The Step-by-Step Authorization Process

  1. Generation: When a user initiates a login, the SPA generates a unique code_verifier locally and computes the corresponding code_challenge.
  2. Authorization Request: The SPA redirects the user to the Authorization Server’s /authorize endpoint, including the code_challenge and the code_challenge_method=S256 in the query parameters.
  3. User Authentication: The Authorization Server authenticates the user, stores the code_challenge, and redirects the user back to the SPA with an authorization_code.
  4. Token Request: The SPA sends a POST request to the token endpoint (/oauth/token). This request includes the received authorization_code along with the original, unhashed code_verifier.
  5. Validation and Issuance: The Authorization Server applies the SHA-256 hash to the provided code_verifier and compares the output to the code_challenge it stored in step 3.
  6. 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.