100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

PKCE in Depth

How Proof Key for Code Exchange binds an authorization request to its token exchange and why S256 is mandatory in modern OAuth deployments.

SecurityIntermediate8 min readJul 10, 2026
Analogies

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.

javascript
// 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

Was this page helpful?

Topics covered

#Programming#OAuth20StudyNotes#PKCEInDepth#PKCE#Depth#Problem#Actually#StudyNotes#SkillVeris#ExamPrep