Safeguard
Security Guides•

PKCE explained: code verifier, code challenge and S256

PKCE stops a stolen authorization code from being redeemed. Learn how the verifier and challenge work, why S256 beats plain, and who must use it.

Safeguard Security Team
3 min read

PKCE (Proof Key for Code Exchange, RFC 7636) binds an OAuth authorization code to the client that asked for it. The client sends a hash of a secret up front and reveals the secret only when redeeming the code, so an attacker who steals the code alone cannot use it. Public clients must use it.

The problem it solves

In the authorization code flow, the browser or app receives a short-lived code in a redirect and trades it for tokens. On mobile and other public clients, another app can sometimes register the same redirect scheme and read that code. Without PKCE, whoever holds the code can redeem it.

How the flow works

  1. The client creates a random code_verifier for each request.
  2. It derives a code_challenge from the verifier and sends the challenge and method in the authorization request.
  3. The server stores the challenge with the issued code.
  4. At the token endpoint, the client sends the code and the original verifier.
  5. The server transforms the verifier and compares. A mismatch returns invalid_grant.

An attacker with only the code does not have the verifier, so the exchange fails.

Verifier rules

The verifier is 43 to 128 characters drawn from letters, digits, and the characters -, ., _ and ~. The RFC suggests at least 256 bits of entropy. The easy way is 32 random bytes encoded as base64url, which gives a 43 character string.

import os, base64, hashlib

verifier = base64.urlsafe_b64encode(os.urandom(32)).rstrip(b"=").decode()
challenge = base64.urlsafe_b64encode(
    hashlib.sha256(verifier.encode("ascii")).digest()
).rstrip(b"=").decode()

Use a cryptographic random source, not a general purpose random function.

S256 versus plain

With S256 the challenge is the base64url of the SHA-256 of the verifier. Seeing the challenge does not reveal the verifier. With plain the challenge equals the verifier, so anyone who can observe the authorization request can recover it, which defeats the purpose.

Two details trip people up. If you omit the method, the default is plain. And a client must not fall back to plain after S256 fails. Use S256 everywhere.

Who must use it

RFC 9700, the OAuth security best current practice, says public clients MUST use PKCE and confidential clients SHOULD. Authorization servers must support it and must reject a token request that includes a verifier when no challenge was sent in the authorization request. That last rule blocks downgrade attacks.

Common mistakes

  • Reusing one verifier across requests. Generate a new one each time.
  • Logging the verifier or putting it in a URL.
  • Sending PKCE but not enforcing it on the server, so an attacker simply leaves it out.
  • Keeping the challenge method configurable to plain "for testing" and forgetting it in production.

Quick check

Do server side apps with a client secret still need PKCE?

RFC 9700 recommends it for confidential clients too. It adds protection against code injection and costs almost nothing.

Is PKCE a replacement for the redirect URI check?

No. They address different attacks. Keep exact redirect URI matching as well.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.