first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
# 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
```ts
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
```ts
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.