Skip to main content
PKCE (RFC 7636) is mandatory for every V2 authorization, for every client type. There is no opt-out, and plain is not accepted — only S256. Build it in from the start; it is not something you can add later.
This applies to the authorization code flow only. The device flow has no code_challenge — the device code itself is the secret, and it is bound to your client_id on the server. Nothing on this page applies there.

The two values

The point is that the challenge is useless on its own. Someone who intercepts the redirect and steals your authorization code still cannot exchange it, because they cannot produce the verifier that hashes to the challenge you registered.

Generating them

The verifier must be 43 to 128 characters, using only A-Z a-z 0-9 - . _ ~. The usual approach is 32 random bytes, base64url-encoded, which lands at 43 characters.

Using them

Send the challenge on the authorize redirect:
Then send the verifier on the exchange. This example is an app or device registration — Mobile, desktop & browser apps or TV, devices & command line — neither of which has a secret. A Server apps & services registration must also authenticate, either with Authorization: Basic or a client_secret parameter; see the authorization code guide for the two variants side by side.
Store the verifier somewhere that survives the browser round-trip — a server-side session, or secure storage on the device. Losing it means the user has to authorize again.

What goes wrong

A failed exchange still consumes the authorization code. Once the request passes client authentication, the code is invalidated before the PKCE check runs. So if your verifier is wrong, you cannot fix it and retry the same code — it is already gone, and the user has to go through the consent screen again.This is correct per RFC 6749 §4.1.2, and it means a PKCE bug costs a full round-trip every time you test it. Get the verifier right in isolation before wiring the flow together — the RFC test vector below lets you do that without touching the API.
The one exception: 401 invalid_client does not consume the code. Client authentication runs before the code is touched, so a request rejected for a missing or wrong client_secret never reaches it.That matters while you are getting a server app’s credentials right: the code survives, and you can retry the same one once the secret is fixed, as long as you are inside the 10-minute window.
A malformed verifier reports invalid_grant, not invalid_request. If the verifier is shorter than 43 characters, longer than 128, or contains a character outside A-Z a-z 0-9 - . _ ~, the error you get back is:
That is the same error as a genuinely mismatched verifier, so the message will not tell you which of the two happened. If PKCE verification fails, check the length and alphabet of your verifier before assuming your hashing is wrong.
The three encoding mistakes that produce a mismatch, in rough order of how often they happen:
  1. Standard base64 instead of base64url. + and / must be - and _.
  2. Leaving the = padding on. Strip it from both values.
  3. Hashing the wrong thing. The challenge is the SHA-256 of the verifier string, not of the random bytes you generated it from.
A quick self-check: if your verifier is dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk, the challenge must be E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM. That pair is the worked example from RFC 7636 Appendix B, so any correct implementation reproduces it.
An unregistered redirect_uri masks a missing code_challenge. Redirect URI validation runs first, so while you are still getting the redirect URI wrong you will not find out that your PKCE parameters are also missing. Fix the redirect URI first, then debug PKCE.

See also

OAuth flow

The full walkthrough this page plugs into.

AUTH V2 overview

Client types, token lifetimes, and which flow to use.

Migrating from V1

Porting existing code, including what changes about PKCE.

Scopes

What to put in the scope parameter on the authorize URL.