What Problem PKCE Actually Solves
Proof Key for Code Exchange, defined in RFC 7636, was designed for public clients such as mobile apps and single-page apps that cannot safely hold a client_secret, but it is now recommended for confidential clients too because it defeats authorization code interception regardless of client type. The client generates a random code_verifier, derives a code_challenge from it, and sends only the challenge in the initial authorization request. When it later exchanges the code for tokens, it must present the original code_verifier, and the authorization server recomputes the challenge to confirm the same client that started the flow is the one finishing it.
Cricket analogy: It's like a fast bowler marking their run-up with a private chalk mark only they know, then proving it's their delivery by matching the mark at the crease; PKCE proves the same client that started the flow is finishing it.
The S256 Code Challenge Method
PKCE supports two challenge methods: plain, where the challenge equals the verifier verbatim, and S256, where the challenge is the base64url-encoded SHA-256 hash of the verifier. S256 is strongly preferred and mandatory in OAuth 2.1 because plain offers no protection if an attacker can observe the authorization request URL itself, since the challenge and verifier would be identical. With S256, even if the code_challenge is exposed in the authorization request, an attacker cannot reverse the hash to recover the verifier needed to complete the exchange.
Cricket analogy: A team's pitch report given openly to the opposition, versus a coded version only the analyst can decode, is the difference between plain and S256; only the hashed version keeps the raw data useless if intercepted.
PKCE and Confidential Clients
It might seem like PKCE is redundant for a confidential client that already authenticates with a client_secret, but OAuth 2.1 mandates PKCE for all clients because client authentication and PKCE protect against different threats. The client_secret proves the app calling the token endpoint is a legitimate registered client, while PKCE proves the specific caller finishing the exchange is the same one that started the specific authorization request, closing the authorization code interception gap even when the client_secret itself is never compromised.
Cricket analogy: Having both a valid player ID card and a specific match-day accreditation badge covers two different checks; the ID proves you're on the team roster, the badge proves you're accredited for this particular game, just like client_secret and PKCE.
// Generating a PKCE pair in a browser-based client
function base64url(buffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
async function createPkcePair() {
const verifierBytes = crypto.getRandomValues(new Uint8Array(32));
const codeVerifier = base64url(verifierBytes);
const encoder = new TextEncoder();
const digest = await crypto.subtle.digest('SHA-256', encoder.encode(codeVerifier));
const codeChallenge = base64url(digest);
return { codeVerifier, codeChallenge, method: 'S256' };
}
// Store codeVerifier (e.g. sessionStorage) before redirecting to /authorize
// with code_challenge and code_challenge_method=S256.The code_verifier must be a cryptographically random string between 43 and 128 characters from the unreserved URL character set. Reusing a fixed or predictable verifier across sessions defeats the entire purpose of PKCE.
- PKCE binds the authorization request to the token exchange using a verifier-challenge pair.
- S256 hashes the verifier before sending it as a challenge; plain sends it unmodified and offers no real protection.
- OAuth 2.1 mandates PKCE for all client types, not just public clients.
- PKCE and client_secret authentication protect against different threats and are complementary.
- The code_verifier must be a high-entropy random string generated fresh per authorization request.
- PKCE defeats authorization code interception even when an attacker fully observes the authorization request.
Practice what you learned
1. What does the code_verifier prove when presented at the token endpoint?
2. Why is the S256 challenge method preferred over plain?
3. Why does OAuth 2.1 require PKCE even for confidential clients that already use a client_secret?
4. What property must a code_verifier have to be effective?
Was this page helpful?
You May Also Like
Common OAuth Vulnerabilities
A survey of the recurring implementation mistakes that turn OAuth 2.0 deployments into attack surfaces, from code interception to login CSRF.
State and CSRF Protection
How the state parameter defeats login CSRF in OAuth flows, the entropy and storage requirements that make it effective, and why it complements PKCE.
Securing Token Endpoints
Hardening the OAuth token endpoint with strong client authentication, brute-force resistance, refresh token rotation, and sender-constrained tokens.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics