Files
skills/docs/identity/auth/oauth-client/src/utils/pkce.md
2026-05-29 08:20:56 +01:00

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 injected generateRandom.
  • generateCodeChallenge — async SHA-256 (S256) challenge with plain fallback.
  • 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.