---
title: "OAuth 2.0 + PKCE Implementation Guide"
url: https://memory.wiki/Oss2bUN8
updated: 2026-04-26T23:16:43.299Z
hub: https://memory.wiki/hub/raymindai
concept_count: 12
source: "memory.wiki"
---
# OAuth 2.0 + PKCE Implementation Guide

## Why PKCE?

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. It's **mandatory** for public clients (SPAs, mobile apps, CLI tools) and recommended for all OAuth flows since 2023.

## Flow Diagram

```mermaid
sequenceDiagram
    participant User
    participant App
    participant AuthServer
    participant API

    App->>App: Generate code_verifier (random)
    App->>App: code_challenge = SHA256(code_verifier)
    App->>AuthServer: /authorize?code_challenge=...&method=S256
    AuthServer->>User: Login prompt
    User->>AuthServer: Credentials
    AuthServer->>App: Authorization code
    App->>AuthServer: /token + code + code_verifier
    AuthServer->>AuthServer: Verify SHA256(code_verifier) == code_challenge
    AuthServer->>App: Access token + Refresh token
    App->>API: Request + Bearer token
    API->>App: Protected resource
```

## Implementation

### 1. Generate PKCE Values

```typescript
async function generatePKCE() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);

  const verifier = base64url(array);
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const hash = await crypto.subtle.digest("SHA-256", data);
  const challenge = base64url(new Uint8Array(hash));

  return { verifier, challenge };
}

function base64url(bytes: Uint8Array): string {
  return btoa(String.fromCharCode(...bytes))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}
```

### 2. Authorization Request

```typescript
const { verifier, challenge } = await generatePKCE();

// Store verifier for later
sessionStorage.setItem("pkce_verifier", verifier);

const params = new URLSearchParams({
  response_type: "code",
  client_id: CLIENT_ID,
  redirect_uri: REDIRECT_URI,
  scope: "openid profile email",
  state: crypto.randomUUID(),
  code_challenge: challenge,
  code_challenge_method: "S256",
});

window.location.href = `${AUTH_URL}/authorize?${params}`;
```

### 3. Token Exchange

```typescript
async function exchangeCode(code: string): Promise<TokenResponse> {
  const verifier = sessionStorage.getItem("pkce_verifier");

  const res = await fetch(`${AUTH_URL}/token`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      client_id: CLIENT_ID,
      code,
      redirect_uri: REDIRECT_URI,
      code_verifier: verifier!,
    }),
  });

  sessionStorage.removeItem("pkce_verifier");
  return res.json();
}
```

## Security Checklist

- [x] Use `S256` method, never `plain`
- [ ] Generate verifier with cryptographic randomness (min 43 chars)
- [ ] Validate `state` parameter to prevent CSRF
- [x] Store tokens securely (httpOnly cookies or encrypted storage)
- [x] Implement token rotation on refresh
- [ ] Add DPoP binding for token theft protection (optional, advanced)

---

*References: RFC 7636, RFC 9126, OAuth 2.1 Draft*

---

## Summary
PKCE (Proof Key for Code Exchange) is mandatory for public clients like SPAs and mobile apps to prevent authorization code interception attacks, requiring generation of a random code verifier, deriving a code challenge via SHA-256 hashing, and including both in the OAuth 2.0 authorization and token exchange flow.

## Themes
- PKCE security mandate
- Public client protection
- OAuth 2.0 modernization

## Key takeaways
- PKCE prevents authorization code interception attacks by requiring clients to prove code ownership through a cryptographic challenge-response mechanism.
- Public clients including SPAs, mobile apps, and CLI tools must implement PKCE, while it is recommended for all OAuth flows since 2023.
- The S256 method using SHA-256 hashing must be used, never the plain method, for code challenge generation.
- Implementation requires three steps: generating PKCE values with cryptographic randomness, sending the challenge in the authorization request, and sending the verifier in the token exchange.
- The code verifier must be minimum 43 characters and generated with cryptographic randomness.

## Insights
- The document marks PKCE as mandatory for public clients but only recommended for others, suggesting a risk-tiered approach to OAuth security rather than universal enforcement.
- The security checklist reveals incomplete implementation guidance, with several items unchecked, indicating this is a reference for developers to customize rather than a prescriptive standard.
- Code storage in sessionStorage during the authorization flow represents a deliberate trade-off between security and implementation simplicity for browser-based applications.

## Open questions / gaps
- What specific vulnerabilities or incidents prompted PKCE to become mandatory for public clients in 2023?
- How should applications handle PKCE implementation when integrating with legacy OAuth providers that don't support it?
- What are the practical differences in implementation complexity between the S256 and plain methods beyond security?

## Concepts in this document
- **PKCE** _(concept)_
  Cryptographic protocol extension that prevents authorization code interception attacks by binding tokens to a code verifier.
- **OAuth 2.0** _(concept)_
  Authorization framework that PKCE extends to secure public clients and improve overall OAuth flow security.
- **Code Verifier** _(concept)_
  Random cryptographic string generated by the client and stored locally to prove ownership during token exchange.
- **Code Challenge** _(concept)_
  SHA-256 hash of the code verifier sent to the auth server to bind the authorization request.
- **S256 Method** _(concept)_
  SHA-256 hash-based PKCE challenge method that is mandatory over plain text for security.
- **Authorization Code Flow** _(concept)_
  OAuth 2.0 flow pattern where the app exchanges an authorization code for an access token, enhanced by PKCE.
- **Public Clients** _(concept)_
  Client applications like SPAs and mobile apps that cannot securely store client secrets and require PKCE protection.
- **Token exchange** _(concept)_
  Step where authorization code is exchanged for access token by proving ownership via code_verifier.
- **State Parameter** _(concept)_
  CSRF protection mechanism that validates consistency between authorization request and callback.
- **Access token** _(concept)_
  Credential issued by auth server after successful code exchange, used to access protected resources.
- **Authorization Code Interception** _(concept)_
  Attack vector that PKCE mitigates by ensuring stolen authorization codes cannot be exchanged without the code verifier.
- **Public Client** _(concept)_
  Client application unable to securely store credentials (SPAs, mobile apps, CLI tools) for which PKCE is mandatory.

## Concept relations (within this doc's concepts)
- **PKCE** strengthens **OAuth 2.0**
- **PKCE** generates **Code Challenge**
- **Authorization Code Flow** secured by **PKCE**
- **S256 Method** implements **Code Challenge**
- **Code Challenge** created via **S256 Method**
- **Authorization Code Interception** prevented by **S256 Method**
- **Code Challenge** uses **S256 Method**
- **PKCE** derives challenge from **Code Challenge**
- **Authorization Code Flow** results in **Access token**
- **Code Verifier** generates **Code Challenge**
- **Code Challenge** sent during **Authorization Code Flow**
- **Token exchange** requires proof via **Code Verifier**
- **Authorization Code Flow** sends during request **Code Challenge**
- **Authorization Code Flow** precedes **Token exchange**
- **Authorization Code Interception** prevented by **PKCE**
- **PKCE** required for **Public Client**
- **Code Verifier** mitigates attack via **Authorization Code Interception**
- **State Parameter** protects during **Authorization Code Flow**
- **PKCE** mandatory for **Public Clients**
- **Code Challenge** sent in request to **Authorization Code Flow**

_Hub canonical:_ https://memory.wiki/hub/raymindai
_Concept digest:_ https://memory.wiki/raw/hub/raymindai?digest=1&compact=1
