Skip to content

PKCE (Proof Key for Code Exchange)

PKCE (pronounced "pixy") prevents authorization codes from being stolen and used by attackers.


Why PKCE?

When you use the Authorization Code Flow, the authorization code gets sent back to your app via a redirect URL. The problem: that code can be intercepted.

  • On mobile, malicious apps can register the same URL scheme and grab the code
  • In browsers, the code ends up in history, logs, or could be captured by browser extensions
  • Network proxies or middleware might see the redirect URL

PKCE fixes this by adding a secret that only your app knows. You create a random value (code_verifier), hash it (code_challenge), and send the hash with your authorization request. When you exchange the code for tokens, you send the original value. The server checks that they match - if someone stole your code, they won't have the verifier.

It's like a coat check: you get a ticket stub, they keep one. To get your coat, both parts need to match.


How It Works

  1. Generate a random code_verifier (43-128 characters)
  2. Hash it with SHA-256 and base64url encode it - that's your code_challenge
  3. Send code_challenge and code_challenge_method=S256 with your authorization request
  4. When exchanging the code, include the original code_verifier
  5. Server hashes the verifier and checks it matches the challenge

Generating PKCE Values

bash

# Generate code verifier
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-43)

# Generate code challenge
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -binary -sha256 | base64 | tr '+/' '-_' | tr -d '=')

echo "Code Verifier: $CODE_VERIFIER"
echo "Code Challenge: $CODE_CHALLENGE"

Python

import secrets
import hashlib
import base64

code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).decode().rstrip("=")

Requirements

PKCE is required for all Authorization Code Flow requests. Only S256 is supported.


References