Two CVEs against the same identity provider, filed six months apart. CVE-2023-48228 and CVE-2024-23647, both against authentik, describe the same failure: the server accepted PKCE in one request and its absence in the next. RFC 7636 had been published nine years before the first advisory.

PKCE binds an authorization code to a secret the client generates before the flow starts. Authorization servers that accept flows without code_challenge downgrade clients to the pre-PKCE threat model. The same applies when they accept token exchanges without code_verifier after a challenge was registered. RFC 9700 mandates enforcement at both endpoints. Most servers ship with enforcement disabled.

PKCE Binds the Code to a Verifier the Server Never Sees in the Authorization Request

The security model of PKCE depends on one fact. The code_verifier is never transmitted in the authorization request, so intercepting that request does not yield the secret needed to exchange the code.

RFC 7636 §4.1 requires the client to generate a code_verifier of 43 to 128 characters of high-entropy cryptographic random data. RFC 7636 §4.2 defines code_challenge = BASE64URL(SHA256(code_verifier)), with S256 required for all conforming servers. The client sends code_challenge in the authorization request; the server stores the challenge alongside the issued code.