MP-301h · Module 1
PKCE & Public Clients
3 min read
PKCE (Proof Key for Code Exchange, pronounced "pixie") solves the authorization code interception problem for public clients — clients that cannot keep a secret. MCP CLI tools and desktop applications are public clients: the client_secret would be embedded in the binary, visible to anyone who decompiles it. PKCE replaces the client secret with a per-request cryptographic proof. The client generates a random code_verifier (43-128 characters, URL-safe), hashes it with SHA-256 to produce the code_challenge, and sends the challenge with the authorization request. When exchanging the code for tokens, the client sends the original code_verifier. The authorization server hashes it and compares against the stored challenge.
The security property is elegant: even if an attacker intercepts the authorization code, they cannot exchange it without the code_verifier — which was never transmitted through the front channel. The code_challenge is a hash, so the verifier cannot be derived from it. And since the verifier is generated fresh for each authorization request, it cannot be reused across sessions. PKCE is now required for all OAuth 2.0 clients, not just public ones — RFC 9126 made it mandatory. MCP servers should reject authorization requests that do not include code_challenge with method S256.
// PKCE code verifier and challenge generation
import crypto from "node:crypto";
function generateCodeVerifier(): string {
// 32 bytes = 43 base64url characters (minimum length)
return crypto.randomBytes(32)
.toString("base64url");
}
function generateCodeChallenge(verifier: string): string {
return crypto.createHash("sha256")
.update(verifier)
.digest("base64url");
}
// Usage in authorization request
const verifier = generateCodeVerifier();
const challenge = generateCodeChallenge(verifier);
// Send challenge with auth request
// authUrl.searchParams.set("code_challenge", challenge);
// authUrl.searchParams.set("code_challenge_method", "S256");
// Send verifier with token exchange
// body.set("code_verifier", verifier);