Verifier and challenge
Generate a cryptographically random verifier of 43–128 characters using only letters, digits, ., _, ~, and -. Compute BASE64URL(SHA256(ASCII(code_verifier))) without padding. Send only the challenge at authorization time; send the original verifier during the token exchange.
const bytes = crypto.getRandomValues(new Uint8Array(32));
const base64url = bytes => btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const verifier = base64url(bytes);
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
const challenge = base64url(new Uint8Array(digest));import { randomBytes, createHash } from 'node:crypto';
const verifier = randomBytes(32).toString('base64url');
const challenge = createHash('sha256').update(verifier, 'ascii').digest('base64url');$verifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
$challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');import base64, hashlib, secrets
def b64url(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('ascii')
verifier = b64url(secrets.token_bytes(32))
challenge = b64url(hashlib.sha256(verifier.encode('ascii')).digest())Keep the transaction together
Store the verifier with the random state in a short-lived login transaction. At callback, validate state first, consume the transaction once, then exchange the code. A mismatched verifier is rejected by the token endpoint.