PKCE, short for Proof Key for Code Exchange and pronounced "pixie", is an extension to the OAuth 2.0 Authorization Code flow. It was created to protect mobile and single-page apps, and current security guidance now recommends it for practically every client that uses the authorization code flow, including server-side web applications.
This article explains the problem PKCE solves, how it works, and what it means for SaaS integrations.
In the Authorization Code flow, the provider sends an authorization code back to your redirect URI. Your application then exchanges that code for tokens.
The weak point is the moment between those two steps. If an attacker obtains the code, for example through a malicious app registered for the same custom URL scheme on a phone, a leaked browser history entry, or a misconfigured redirect, they may be able to exchange it for tokens themselves.
Confidential clients, which authenticate with a client secret during the exchange, have some protection: the attacker would also need the secret. Public clients such as mobile apps and browser-based apps can't keep a secret, so a stolen code is enough.
PKCE binds the authorization code to the specific client instance that requested it, using a one-time secret that never travels through the browser redirect.
Create a verifier. Before redirecting the user, your application generates a high-entropy random string called the code_verifier and keeps it private.
Derive a challenge. It computes the code_challenge, the base64url-encoded SHA-256 hash of the verifier.
Send the challenge. The authorization request includes code_challenge and code_challenge_method=S256. The authorization server stores the challenge alongside the code it issues.
Send the verifier. When exchanging the code for tokens, your application includes the original code_verifier.
Server checks. The authorization server hashes the verifier and compares it with the stored challenge. If they don't match, the exchange fails.
An attacker who intercepts the code doesn't have the verifier, and can't derive it from the challenge, so the stolen code is useless.
The PKCE challenge travels with the authorization request in step 2; the verifier is only sent directly to the token endpoint in step 5.
PKCE defines two challenge methods. plain sends the verifier itself as the challenge, which offers little protection if the authorization request is observed. S256 sends a hash. Use S256 wherever the provider supports it.
The verifier must be 43 to 128 characters from the unreserved URL character set, and should come from a cryptographically secure random generator. A common approach is 32 random bytes, base64url-encoded without padding.
Store it server-side, tied to the user's session and the specific authorization request, and discard it after the exchange.
The original specification targeted public clients. Since then, the OAuth 2.0 Security Best Current Practice (RFC 9700) recommends PKCE for confidential clients too, because it also protects against authorization code injection attacks that a client secret alone doesn't prevent. The draft OAuth 2.1 specification makes PKCE part of the standard Authorization Code flow.
In practice:
It costs a few lines of code.
It defends against attacks a client secret doesn't cover.
PKCE doesn't replace state. The state parameter ties the callback to the user's session to prevent cross-site request forgery, and lets you carry context through the redirect. PKCE ties the code to the client instance. Use both, unless the provider's guidance says otherwise.
Most major OAuth providers support PKCE with S256, but not all, and some handle it slightly differently. A few require it for certain app types. Check each provider's documentation, and test that the exchange actually fails when you send a wrong verifier. That confirms the provider is enforcing it.
When you build OAuth yourself, PKCE is one more piece to implement and test per provider. Pathfix OAuth runs the authorization flow for you across 350+ providers, along with token storage, refresh and authenticated API passthrough, using your own OAuth apps and credentials. Check the documentation for current details of how individual providers are configured.