← Concepts

阅读中文版本

OAuth PKCE Pitfalls

PKCE stops authorization-code interception, but weak verifier handling, plain challenges, and missing state checks can quietly reopen the attack it was meant to close.

security

Definition

OAuth PKCE pitfalls are implementation mistakes that defeat Proof Key for Code Exchange, such as accepting codes without a verifier, allowing the plain challenge method, or failing to bind the verifier to the authorization request.

Context and scope

Use this checklist when building or reviewing public-client OAuth flows for mobile apps, single-page apps, and desktop clients, where the authorization code can be intercepted.

Common misconceptions

  • Adding PKCE makes any redirect URI safe; custom schemes can still be claimed by another app
  • The plain code_challenge_method is acceptable for confidential clients; S256 should be the default everywhere
  • PKCE replaces state; it does not protect against login CSRF or mix-up attacks

Anti-patterns

  • Exchanging an authorization code when code_verifier is missing or empty
  • Generating a new verifier at token exchange instead of reusing the one bound to the request
  • Logging the code_verifier alongside the authorization code

Related concepts

Related examples

Authoritative source

https://www.rfc-editor.org/rfc/rfc9700.html

What PKCE protects

In the authorization-code flow, a malicious app on the same device can register a custom URI scheme and intercept the redirect containing the code. PKCE adds a secret the intercepting app does not have: the client sends a hash of a random verifier at authorization time and the original verifier at token exchange. A stolen code alone is useless.

Where implementations go wrong

The first pitfall is accepting a token request with no code_verifier when the authorization request included a challenge. That turns PKCE into decoration. The second is allowing code_challenge_method=plain, where the challenge equals the verifier and an attacker who sees the authorization request learns the secret. S256 should be mandatory.

A third is creating a fresh verifier at exchange time, which can never match the stored challenge. Others include skipping state (leaving login CSRF and mix-up attacks open), registering wildcard or shared redirect URIs, allowing code reuse, and logging the verifier next to the code.

When to use it

Every authorization-code client should use PKCE, including confidential web clients, because it defends against code injection through open redirects. It matters most for public clients that cannot keep a client secret. Implicit and password grants should be retired rather than patched.

Trade-offs

PKCE adds a round of state management on the client and requires careful handling of the verifier across tabs and app restarts. Native apps still need claimed HTTPS or verified app links, because any app can register a custom scheme. Server-side validation must reject mismatches rather than fall back to issuing tokens.

Example and misconceptions

An attacker’s app registers com.example.app://callback and captures a code. It tries to exchange it without the verifier and receives invalid_grant. If the server had accepted missing verifiers, the code would have yielded tokens.

The common misconception is that PKCE authenticates the client. It binds the code to the client instance that started the flow; client authentication is a separate control.

Related concepts: OAuth 2.0 Authorization Code Flow with PKCE, OAuth Scopes, and OpenID Connect.