OAuth PKCE: Binding an Authorization Code to Its Verifier
Learn how a per-request verifier and S256 challenge constrain stolen-code redemption, and why PKCE does not itself protect issued access tokens.
Publication & corrections: infostoa · Editorial standards
Individual author and specialist reviewer information is not provided for this article.
OAuth PKCE requires proof of a verifier when an authorization code is exchanged for tokens. A party that intercepts only the code should not be able to complete the exchange without the correct verifier. This protects the code exchange; it does not solve every case of token theft or incorrect authorization.
Prepare a different secret for each request
RFC 7636 defines a high-entropy code_verifier for each authorization request. Its syntax permits 43 to 128 characters from the specified unreserved character set. A predictable string of the right length is not equivalent to a cryptographically random secret. Keep the generated value associated with its request until the exchange stage.
Reusing one value for every login weakens the per-request binding. Concurrent login windows can also cause mistakes if a shared storage slot is overwritten and a code is paired with another request's verifier. Checking that storage association is a practical implementation recommendation in addition to checking the random generator.
Send the challenge in the authorization request
S256 hashes the ASCII verifier with SHA-256 and represents the result with base64url encoding to form code_challenge. The authorization request sends that challenge and the method name. The original verifier is used later in the token request, rather than exposed unchanged at this stage. The two similarly named values serve different purposes. Use base64url without padding. S256 transformation
The RFC requires clients capable of S256 to use it. Confusing ordinary base64 with base64url, or applying an incorrect text encoding, can produce a value that does not match the server's calculation. Checking the RFC's example input and output isolates transformation mistakes without needing to repeat an entire interactive login.
The token server checks the binding
The server associates the challenge with the authorization code. At exchange time it transforms the received verifier with the recorded method and compares the result. RFC 7636 specifies an invalid_grant response for a mismatch. A client sending a challenge is only one part of the protection: the token endpoint must enforce the check. A match permits remaining checks; it does not guarantee token issuance. Token verification
RFC 9700 says public clients MUST use PKCE and confidential clients are RECOMMENDED to use it. state binds the response to the initiating application session; in OpenID Connect, nonce binds the authentication request to the ID Token and its returned claim must be validated. Rely on PKCE for CSRF protection only when the authorization server is known to support it; otherwise use a one-time state value securely bound to the user agent. A server must reject a token request that supplies a verifier when the original authorization request had no challenge, preventing a downgrade. That is distinct from falling back from S256 to plain.
These are proposed rejection tests, not executed results: missing or mismatched verifier, a verifier from another request, a verifier after challenge omission, and retrying with plain after S256 fails. Also test a valid exchange, and record a pass only after running the cases. In logs, identify the request without storing raw codes or verifiers; record outcomes and the binding being tested instead.
Diagnose failures without downgrading the method
The RFC prohibits downgrading to plain after trying S256. For a mismatch, inspect the stored request association, encoding, method, and server-side challenge first. Disabling or weakening the protection to make a login succeed would not establish a correct exchange.
A useful completion criterion combines successful valid-code exchange with rejection of an incorrect verifier. Also check that concurrent logins do not mix their values. Treating PKCE as one control connecting request initiation to code redemption makes the remaining security work visible instead of presenting it as a guarantee for the entire login system.
Sources
- RFC 7636: verifier, S256, and server verification.
- OWASP OAuth2 guidance: transaction binding and protection boundaries.
- RFC 9700: client requirements, conditional CSRF protection, and downgrade prevention.