This guide is written by the team that builds Casdoor and describes the standards as published (listed under Sources), with notes on how Casdoor implements them as of November 2, 2026. Spotted a mistake? Tell us.
PKCE (Proof Key for Code Exchange, RFC 7636) is a small extension to the OAuth 2.0 authorization code flow that stops a stolen authorization code from being exchanged for tokens. It is pronounced "pixy", and it is now expected in almost every OAuth client, not just mobile apps.
The idea is simple: the client creates a random secret before the login starts, sends only a hash of it to the authorization server, and proves it still has the original secret when it exchanges the code.
The problem PKCE solves
In the authorization code flow, the authorization server redirects the browser back to the client with a short-lived code. The client then exchanges that code for tokens at the token endpoint.
A public client, such as a mobile app or a single-page app, cannot keep a client secret. If an attacker can intercept the redirect, or register a competing app that claims the same custom URL scheme, it can steal the code and exchange it before the legitimate client does.
PKCE binds the code to the client instance that started the flow. Even if the code is intercepted, it is useless without the original verifier.
How PKCE works
- The client generates a random code verifier: 43 to 128 characters from the unreserved set.
- It derives a code challenge, normally the BASE64URL encoding of the SHA-256 hash of the verifier. The method is called S256.
- It sends the challenge and the method in the authorization request.
- The authorization server stores the challenge with the authorization code.
- When the client exchanges the code, it sends the original verifier.
- The server hashes the verifier and compares it with the stored challenge. If they do not match, the request fails.
For the S256 method, the challenge is: BASE64URL(SHA256(ASCII(code_verifier))). The plain method sends the verifier itself as the challenge and exists only for clients that cannot compute SHA-256; do not use it for new work.
A minimal authorization request carries response_type=code, client_id, redirect_uri, scope, state, code_challenge and code_challenge_method=S256. The token request carries grant_type=authorization_code, code, redirect_uri, client_id and code_verifier. For a public client there is no client secret; the verifier is the proof.
Why it matters now
PKCE was designed for mobile apps, but the security guidance has moved:
- RFC 9700, the OAuth 2.0 Security Best Current Practice, recommends PKCE for public clients and for confidential clients that use the authorization code flow.
- OAuth 2.1 requires PKCE for every authorization code flow, including confidential clients.
- OAuth 2.0 for Browser-Based Apps recommends it for single-page applications that use the code flow.
The old implicit flow, which returned tokens directly in the browser, is deprecated for new applications. The code flow with PKCE is the replacement.
Common mistakes
- Forgetting the method. If the client sends a challenge but no code_challenge_method, some servers assume plain. Send S256 explicitly.
- Reusing a verifier. Generate a new verifier for every authorization request. A verifier is a per-flow secret, not a client-wide key.
- Logging the verifier. It is a credential. Do not put it in logs, analytics or crash reports.
- Storing it in the wrong place. In a browser, keep it in memory or session storage for the duration of the flow; do not put it in a long-lived local storage key.
- Thinking PKCE replaces client authentication. For a confidential client, PKCE is an additional protection, not a substitute for the client secret or private key.
- Mixing PKCE with implicit. If the response type returns tokens directly, there is no code to bind; use the code flow.
PKCE and DPoP
PKCE and DPoP (RFC 9449) are sometimes mentioned together, but they protect different things:
- PKCE binds the authorization code to the client that started the flow.
- DPoP binds an access token to a private key, so a stolen token cannot be replayed by another party.
Use both when the threat model includes token theft after issuance. PKCE alone does not protect a token that has already been issued.
PKCE in Casdoor
Casdoor supports the authorization code flow with PKCE. The OIDC discovery document advertises the supported code challenge methods, and the authorization endpoint accepts a code challenge and method. Enable the authorization code grant on the application, then use S256 in the client.
If you are integrating a framework that handles PKCE for you, check that it uses S256, generates a fresh verifier per request, and sends the verifier only to the token endpoint. For the endpoints and grant settings, see the Casdoor OIDC documentation. Casdoor is open source, and Casdoor Cloud starts at $6.67/month billed yearly for a dedicated instance.
FAQ
Is PKCE required for confidential clients? RFC 9700 recommends it, and OAuth 2.1 requires it for authorization code flows. It is cheap to add and protects against code interception even when a client secret exists.
Does PKCE replace state? No. state protects against CSRF and correlates the response with the request. PKCE protects the code exchange. Use both.
Can I use the plain method? Only for clients that cannot compute SHA-256. It offers much less protection because the challenge and the verifier are the same value.
What happens if the verifier does not match? The token endpoint returns an error such as invalid_grant, and the code is not exchanged. The user has to start the flow again.
Is PKCE enough for a single-page app? It is necessary but not sufficient. Use the authorization code flow with PKCE, exact redirect URIs, a strict Content Security Policy, and short-lived tokens. For higher assurance, consider a backend-for-frontend that keeps tokens off the browser entirely.
Sources
- RFC 7636: PKCE
- RFC 9700: OAuth 2.0 Security Best Current Practice
- OAuth 2.1 draft
- RFC 9449: DPoP
- Casdoor OIDC documentation
- Casdoor OAuth documentation
Product names are trademarks of their respective owners.