PKCE
PKCE is defined by RFC 7636 — Proof Key for Code Exchange: the client generates a random secret (the code verifier) and sends a hash of it when starting the authorization flow, then the real secret when exchanging the code. An attacker who intercepts the authorization code cannot redeem it without the verifier.
LumoAuth supports S256 and plain challenge methods. S256 is strongly recommended and is the only method appropriate for public clients.
note
PKCE is required for all public clients (SPAs, mobile apps, CLI tools). Confidential clients should also use PKCE for defense in depth.
How PKCE Works
- The client generates a random
code_verifier(43–128 characters, URL-safe). - It hashes the verifier with SHA-256 and base64url-encodes the result to produce
code_challenge. - It sends
code_challengeandcode_challenge_method=S256in the authorization request. - It sends the original
code_verifierwhen exchanging the code at the token endpoint.
LumoAuth validates that SHA256(code_verifier) == code_challenge.
Generating PKCE Values
JavaScript (Browser / Node.js)
async function generatePKCE() {
// 1. Generate a random code verifier
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const codeVerifier = base64UrlEncode(array);
// 2. Hash to produce code challenge
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await crypto.subtle.digest('SHA-256', data);
const codeChallenge = base64UrlEncode(new Uint8Array(digest));
return { codeVerifier, codeChallenge };
}
function base64UrlEncode(buffer) {
return btoa(String.fromCharCode(...buffer))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
Python
import secrets
import hashlib
import base64
def generate_pkce():
code_verifier = secrets.token_urlsafe(32)
digest = hashlib.sha256(code_verifier.encode()).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()
return code_verifier, code_challenge
Authorization Request with PKCE
GET /orgs/{orgId}/api/v1/oauth/authorize?
response_type=code&
client_id=your_client_id&
redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&
scope=openid+profile&
state=abc123&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256
Token Request with code_verifier
Token Exchange with PKCE
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=SplxlOBeZQQYbYS6WxSbIA" \
-d "redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback" \
-d "client_id=your_client_id" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gAWpLynrU"