01 Generate a pair
The verifier is drawn from the RFC 7636 unreserved charset (A-Z, a-z, 0-9, and - . _ ~) using crypto.getRandomValues with rejection sampling, so every character is uniformly random. Moving the slider regenerates.
Keep this on the client. It is only sent once, in the back-channel token exchange.
base64url(SHA-256(verifier)): the + and / of base64 become - and _, and the = padding is stripped.
code_challenge=...&code_challenge_method=S256
02 Bring your own verifier
Already have a code_verifier? Paste it below to check it against RFC 7636 (charset and 43-to-128 length) and compute the matching S256 challenge. Handy for debugging an "invalid_grant" from a token endpoint.
Paste a code_verifier above to check it and compute its S256 challenge.
03 Where each value goes
Two steps, two channels. The challenge rides the observable front channel; the verifier only ever appears in the direct back-channel call. The highlighted values update live with your generated pair.
Step 1 / front channel: send the user to the authorize endpoint
https://auth.example.com/authorize
?response_type=code
&client_id=your-client-id
&redirect_uri=https://app.example.com/callback
&scope=openid+profile
&state=your-random-state
&code_challenge=your-code-challenge
&code_challenge_method=S256
Step 2 / back channel: swap the returned code for tokens
POST https://auth.example.com/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=code-from-step-1-redirect
&redirect_uri=https://app.example.com/callback
&client_id=your-client-id
&code_verifier=your-code-verifier <- the server hashes this and compares
04 Why PKCE exists
Proof Key for Code Exchange, defined in RFC 7636. Two minutes, no hand-waving.
The attack it stops: authorization code interception
In the OAuth 2.0 authorization code flow, the authorization server hands your app a one-time code through a redirect. On mobile platforms, more than one app can register for the same custom URL scheme, so a malicious app on the same device can catch that redirect and steal the code. Before RFC 7636, whoever held the code could walk it to the token endpoint and trade it for real access tokens.
Public clients cannot keep a secret
A server-side (confidential) client defends the token endpoint with a client_secret. A public client, meaning a mobile app or a single-page app, ships every byte of itself to the user's device, and anything embedded in a binary or a JS bundle can be extracted. So there is no secret that lets the token endpoint tell your app from the attacker's app. Possession of the code alone wins.
How the verifier and challenge close the hole
PKCE has the client invent a fresh, high-entropy secret for every authorization request: the code_verifier. The client sends only its fingerprint, code_challenge = base64url(SHA-256(verifier)), along with the authorize request. The server stores that challenge next to the code it issues. At the token exchange, the client must present the original verifier; the server hashes it and compares. An attacker who intercepted the redirect holds the code but never saw the verifier, because only the hash crossed the observable channel. The stolen code is dead weight.
Why S256 beats plain
RFC 7636 defines two challenge methods. With plain, the challenge is the verifier, so anywhere the authorize request can leak (browser history, proxy and server logs, referrer headers), the verifier leaks with it and the protection collapses. With S256, only the hash travels the leaky path, and reversing SHA-256 to recover a 43-plus-character random string is not practical. The RFC is blunt about it: clients must use S256 whenever they are capable of it; plain exists only for legacy clients that cannot hash.
Current guidance goes further: use PKCE on every authorization code flow, confidential clients included, since it also blocks code-injection tricks. Primary source: RFC 7636, Proof Key for Code Exchange by OAuth Public Clients.