PKCE for Single-Page Applications (SPAs)

How to implement OAuth 2.0 Authorization Code + PKCE in a single-page application. Step-by-step with code examples for code_verifier and code_challenge generation.

PKCE

Detailed Explanation

PKCE for Single-Page Applications

PKCE (Proof Key for Code Exchange, pronounced "pixy") is an extension to the Authorization Code flow that makes it safe for public clients — applications that cannot securely store a client_secret, such as browser-based SPAs, mobile apps, and desktop applications.

Why SPAs Need PKCE

SPAs run entirely in the browser, which means:

  • They cannot securely store a client_secret (any secret in JavaScript is accessible to users)
  • Authorization codes can be intercepted by malicious browser extensions or through open redirectors
  • The Implicit flow (the old alternative) exposes tokens directly in the URL

PKCE solves the authorization code interception problem by adding a cryptographic proof that the client that started the flow is the same one exchanging the code.

How PKCE Works

1. Generate a code_verifier — a random string (43-128 characters):

const array = new Uint8Array(32);
crypto.getRandomValues(array);
const code_verifier = base64url(array);

2. Derive the code_challenge using SHA-256:

const encoder = new TextEncoder();
const digest = await crypto.subtle.digest(
  "SHA-256",
  encoder.encode(code_verifier)
);
const code_challenge = base64url(new Uint8Array(digest));

3. Authorization Request — include the challenge:

GET /authorize?response_type=code
  &client_id=spa-client
  &redirect_uri=https://app.example.com/callback
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &scope=openid profile
  &state=random-state

4. Token Exchange — include the original verifier:

POST /token
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://app.example.com/callback
&client_id=spa-client
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

The authorization server computes SHA256(code_verifier) and compares it to the stored code_challenge. If they match, the token is issued.

Best Practices

  • Always use S256 (never plain)
  • Store the code_verifier in sessionStorage (cleared on tab close)
  • Use a new code_verifier for every authorization request
  • Combine PKCE with the state parameter for defense in depth

Use Case

A React, Vue, or Angular SPA that authenticates users via an OAuth 2.0 provider (e.g., Auth0, Okta, Azure AD). The app generates a PKCE pair before redirecting to the login page and uses the code_verifier during the token exchange.

Try It — OAuth 2.0 Flow Visualizer

Open full tool