OAuth2 pkce_required
Hydra rejects a public-client flow without PKCE
error: invalid_request
error_description: PKCE is required for public clientsOlympus enforces PKCE for all public OAuth2 clients (ADR 0019). A flow without code_challenge against a public client (token_endpoint_auth_method=none) fails immediately.
Fix
Add PKCE to your authorization request:
// 1. Generate a verifier
const codeVerifier = base64url(crypto.randomBytes(32));
// 2. Hash it for the challenge
const codeChallenge = base64url(
crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))
);
// 3. Store the verifier somewhere durable across the redirect
sessionStorage.setItem('pkce_verifier', codeVerifier);
// 4. Include the challenge in /oauth2/auth
const url = new URL(`${ISSUER}/oauth2/auth`);
url.search = new URLSearchParams({
client_id: CLIENT_ID,
response_type: 'code',
redirect_uri: REDIRECT,
scope: 'openid profile email',
state: generateState(),
code_challenge: codeChallenge,
code_challenge_method: 'S256',
}).toString();
window.location.href = url.toString();On callback:
const verifier = sessionStorage.getItem('pkce_verifier');
const response = await fetch(`${ISSUER}/oauth2/token`, {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: CLIENT_ID,
code,
redirect_uri: REDIRECT,
code_verifier: verifier,
}),
});Common mistakes
Using plain instead of S256
Olympus only accepts S256. The deprecated plain method (sending the verifier as the challenge directly) is rejected as unsupported_challenge_method.
Verifier lost across the redirect
If the verifier was in memory only and the page reloaded between /oauth2/auth and /callback, it's gone. Use sessionStorage or an HttpOnly cookie.
Verifier shorter than 43 characters
RFC 7636 requires the verifier be 43-128 unreserved characters. Anything shorter is rejected.
Client is confidential
If your client has a client_secret (confidential client), PKCE is optional (recommended but not required). The error wouldn't fire for confidential clients. If you're seeing it, you registered as public, verify in Athena → OAuth2 Clients → your client.
Why not just disable PKCE enforcement?
You can't. Olympus enforces it at multiple layers:
- Hydra config:
oauth2.pkce.enforced_for_public_clients: true. - Athena UI: public clients show PKCE as required, not editable.
verify-prod-config.ymlCI: fails the build if PKCE enforcement is turned off in production.
If you genuinely need a non-PKCE public flow, the client probably should be confidential (deploy a backend that holds the secret).