When applications allow users to log in using services like Google, GitHub, or Microsoft, they usually rely on the OAuth 2.0 authorization framework.
But OAuth alone had a major security weakness for public clients such as:
- Mobile apps
- Single Page Applications (SPAs)
- Desktop applications
- CLI tools
That weakness led to the creation of PKCE.
PKCE (pronounced pixy) is now one of the most imporatnat security mechanisms in modern authentication systems. Today, it is mandatory in may OAuth flows and is a core part of OpenID Connect security recommendations.
What is PKCE?
PKCE stands for:
Proof Key for Code Exchange
It was introduced in:
- RFC 7636
PKCE is an extension to OAuth 2.0 designed to protect the Authorization Code Flow from interception attacks.
Why PKCE Was Needed
To understand PKCE, you first need to understand a flaw in traditional OAuth.
User → Client App → Authorization Server
↓
Authorization Code
↓
Client exchanges code
↓
Access TokenExample:
- User clicks “Login with Google”
- Apps redirects user to authorization server
- User authenticates
Authorization server redirects back with:
?code=abc123- Client exchanges
abc123for tokens
The Problem: Authorization Code Interception
This flow assumes the authorization code is safe.
But on public clients:
- Mobile apps
- Browser SPAs
- Desktop apps
… the authorization code can be intercepted.
For example:
Attacker intercepts authorization code
↓
Attacker redeems code before real app
↓
Attacker gets access tokenThis attack is called:
Authorization Code Interception Attack
Why Public Clients Are Dangerous
Confidential clients can safely store secrets.
Examples:
- Backend servers
- Secure server-side apps
Public clients cannot.
Examples:
- JavaScript apps
- Mobile apps
- Electron apps
- Browser extensions
Because:
- Code is visible
- Secretss can be extracted
- Reverse engineering is possible
This means:
client_secret is useless for public appsPCKE solves this problem without needing a client secret.
Code Idea Behind PKCE
PKCE introduces:
- code_verifier
- code_challenge
The client proves:
“I am the same client that initiated the authorization request.”
Even if an attacker steals the authorization code, they cannot exchange it without the original verifier.
High-Level PKCE Flow
Client generates random code_verifier
↓
Transforms it into code_challenge
↓
Sends challenge to authorization server
↓
Authorization server stores challenge
↓
User authenticates
↓
Authorization code returned
↓
Client sends original verifier
↓
Server verifies verifier matches challenge
↓
Token issuedPKCE Components
1 code_verifier
A cryptographically random string.
Example:
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkRequirements:
- High entropy
- Random
- 43-128 characters
- URL safe
2 code_challenge
Derived from the verifier.
Usually:
BASE64URL(SHA256(code_verifier))Example:
E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM3 code_challenge_method
Indicates how challenge was generated.
Usually:
- S256 (recommended)
- plain (legacy, discouraged)
Step-by-Step PKCE Flow
Step 1: Generate code_verifier
Client creates random secret:
code_verifier = random_secure_string()Example:
abcxyz123securelongrandomvalueStep 2: Generate code_challenge
Using SHA-256:
code_challenge =
BASE64URL(
SHA256(code_verifier)
)Step 3: Authorization Request
Client redireccts user:
GET /authorize?
response_type=code
&client_id=myclient
&redirect_uri=https://app.com/callback
&code_challenge=XYZ
&code_challenge_method=S256Important:
- The verifier is not sent
- Only challenge is sent
Step 4: User Authenticates
User logs in and grants permissions.
Authorization server stores:
authorization_code → associated code_challengeStep 5: Authorization Code Returned
Server redirects:
https://app.com/callback?code=AUTH_CODEStep 6: Token Exchange
Client sends:
POST /token
grant_type=authorization_code
code=AUTH_CODE
code_verifier=original_random_secretStep 7: Server Verification
Authorization server:
SHA256(code_verifier)
↓
BASE64URL encode
↓
Compare with original code_challengeIf match:
- Access token issued
If not:
- Request rejected
Why PKCE Works
Suppose attacker intercepts:
authorization_codeBut attacker does NOT know:
code_verifierWithout verifier:
- Token exchange fails
Thus intercepted code bceomes useless.
Leave a comment
Your email address will not be published. Required fields are marked *
