PKCE (pronounced "pixy") prevents authorization codes from being stolen and used by attackers.
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.
- Generate a random
code_verifier(43-128 characters) - Hash it with SHA-256 and base64url encode it - that's your
code_challenge - Send
code_challengeandcode_challenge_method=S256with your authorization request - When exchanging the code, include the original
code_verifier - Server hashes the verifier and checks it matches the challenge
# 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"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("=")PKCE is required for all Authorization Code Flow requests. Only S256 is supported.
- RFC 7636 - PKCE specification
- OAuth 2.0 Security BCP - recommends PKCE for all clients