4.0 KiB
pkce
Purpose
Implements RFC 7636 Proof Key for Code Exchange (PKCE) utilities for the OAuth 2.0 authorization code flow. PKCE prevents authorization-code interception attacks by binding the code request to a code verifier that only the original requester knows. All functions are pure and runtime-agnostic — the only platform API required is globalThis.crypto.subtle for SHA-256 hashing, which is available in every modern runtime (Node 15+, browser, Bun, Deno, edge workers).
Features
base64UrlEncode— URL-safe base64 encoding without padding (RFC 4648 §5).base64UrlDecode— URL-safe base64 decoding, tolerates missing padding.generateCodeVerifier— 43-character random verifier (256 bits of entropy) via injectedgenerateRandom.generateCodeChallenge— async SHA-256 (S256) challenge withplainfallback.verifyCodeChallenge— server-side PKCE verification.generatePkcePair— convenience wrapper returning verifier + challenge + method.generateState— random OAuth state parameter for CSRF protection.generateNonce— random OIDC nonce.
Usage: Basic Example
import { generatePkcePair, generateState } from "@suaveplan/oauth-client/utils/pkce";
const random = (n: number) => globalThis.crypto.getRandomValues(new Uint8Array(n));
const { codeVerifier, codeChallenge, codeChallengeMethod } = await generatePkcePair(random);
const state = generateState(random);
// Store codeVerifier and state in session — they are needed after redirect.
Usage: Advanced Example
import {
generateCodeVerifier,
generateCodeChallenge,
verifyCodeChallenge,
} from "@suaveplan/oauth-client/utils/pkce";
const random = (n: number) => globalThis.crypto.getRandomValues(new Uint8Array(n));
// Generate the pair manually to inspect each part.
const verifier = generateCodeVerifier(random);
const { challenge, method } = await generateCodeChallenge(verifier);
// Server-side verification (e.g. in a BFF or test):
const valid = await verifyCodeChallenge(verifier, challenge, method);
// => true
API Reference
base64UrlEncode(bytes: Uint8Array): string
Encodes a byte array as base64url without = padding. Returns a URL-safe string safe for use in query parameters.
base64UrlDecode(input: string): Uint8Array
Decodes a base64url string (with or without = padding) to a Uint8Array.
generateCodeVerifier(generateRandom: (bytes: number) => Uint8Array): string
Returns a 43-character base64url-encoded code verifier backed by 32 random bytes (256 bits of entropy).
generateCodeChallenge(verifier: string): Promise<{ challenge: string; method: CodeChallengeMethod }>
Computes the PKCE code challenge. Uses S256 (SHA-256) when globalThis.crypto.subtle is available, falling back to plain otherwise.
verifyCodeChallenge(verifier: string, challenge: string, method: CodeChallengeMethod): Promise<boolean>
Returns true when verifier corresponds to challenge under method. Useful for testing or server-side BFF verification.
generatePkcePair(generateRandom: (bytes: number) => Uint8Array): Promise<PkcePair>
Generates a complete PkcePair (verifier + challenge + method) in one call.
generateState(generateRandom: (bytes: number) => Uint8Array): string
Returns a 22-character base64url-encoded random OAuth state parameter (16 bytes, 128 bits of entropy).
generateNonce(generateRandom: (bytes: number) => Uint8Array): string
Returns a 22-character base64url-encoded random OIDC nonce (16 bytes).
Implementation Notes
The base64UrlEncode implementation manually iterates 3-byte groups rather than calling btoa to avoid Buffer or atob edge-cases in non-browser environments. The base64UrlDecode function uses atob after replacing URL-unsafe characters and restoring padding — atob is universally available in every targeted runtime. The SHA-256 digest path uses the crypto.subtle Web Cryptography API directly to remain dependency-free; no third-party hash library is imported.