PKCE (Proof Key for Code Exchange)
What Problem Does PKCE Solve?
PKCE (pronounced “pixy”) protects the OAuth 2.0 / OIDC Authorization Code Flow from authorization code interception attacks.
It was originally introduced for:
- Mobile apps
- SPAs
- Public clients (no client secret)
It is now recommended for all clients, including confidential server-side apps.
The Core Security Problem
Without PKCE
- App redirects user to IdP
- User logs in
- IdP redirects back with
code - App exchanges
codefor tokens
If an attacker intercepts the code via:
- malicious app
- browser extension
- proxy
- redirect URI hijack
They can exchange it for tokens.
Authorization code alone is not proof of possession.
How PKCE Fixes This
PKCE binds the authorization code to a secret that only the legitimate client knows.
It introduces:
code_verifier(random secret)code_challenge(hashed version)
The attacker may steal the code — but they do NOT have the code_verifier.
Step-by-Step Flow
Step 1 — Client Creates a Code Verifier
Random high-entropy string (43–128 chars):
var bytes = RandomNumberGenerator.GetBytes(32);
var codeVerifier = Base64UrlEncode(bytes);
Example:
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Step 2 — Create Code Challenge
Hash the verifier using SHA256:
using var sha = SHA256.Create();
var hash = sha.ComputeHash(Encoding.ASCII.GetBytes(codeVerifier));
var codeChallenge = Base64UrlEncode(hash);
Step 3 — Send Code Challenge to IdP
Authorization request:
response_type=code
client_id=app123
code_challenge=abcXYZ
code_challenge_method=S256
The IdP stores the challenge.
Step 4 — User Authenticates
IdP redirects back with:
https://app.com/callback?code=AUTH_CODE
Step 5 - Token Exchange (Proof Step)
Client sends:
grant_type=authorization_code
code=AUTH_CODE
code_verifier=original_random_string
IdP:
- Hashes the
code_verifier - Compares it with original
code_challenge - If match → issue tokens
- If not → reject
Sequence Diagram
sequenceDiagram
participant Client
participant IdP
Client->>Client: Generate code_verifier
Client->>Client: Create code_challenge (SHA256)
Client->>IdP: /authorize (code_challenge)
IdP->>Client: Redirect with code
Client->>IdP: /token (code + code_verifier)
IdP->>IdP: Validate hash matches
IdP->>Client: Issue tokens
Why It Works (Security Principle)
PKCE introduces:
Proof-of-possession over the authorization code
The attacker might steal:
- The authorization code
But they cannot:
- Produce the correct code_verifier
Thus:
- Code is useless without verifier
S256 vs Plain
| Method | Secure? | Use? |
|---|---|---|
| plain | ❌ No hashing | Avoid |
| S256 | ✅ SHA256 | Always use |
Modern providers require S256.
PKCE in ASP.NET Core (.NET 8/9/10)
If using AddOpenIdConnect:
.AddOpenIdConnect("oidc", options =>
{
options.ResponseType = "code";
options.UsePkce = true; // Default true in modern versions
});
For public SPA:
builder.Services.AddAuthentication()
.AddJwtBearer();
Modern libraries automatically implement PKCE.
When Is PKCE Required?
| Client Type | PKCE |
|---|---|
| SPA | Required |
| Mobile | Required |
| Desktop | Required |
| Server-side web app | Strongly recommended |
| Confidential client with secret | Still recommended |
OAuth 2.1 makes PKCE mandatory.
Real Attack Without PKCE
Scenario:
- Malicious app registers same redirect URI
- User logs in
- Code is sent to malicious app
- Attacker exchanges code for tokens
With PKCE:
- Attacker doesn't know verifier
- Token exchange fails
Advantages
- Prevents code interception
- No client secret required
- Works in public clients
- Backwards compatible
- Lightweight
Disadvantages
- Slight complexity increase
- Requires crypto operations
- Needs correct implementation
In practice:
- All modern frameworks handle it automatically
PKCE vs Client Secret
| Feature | Client Secret | PKCE |
|---|---|---|
| Works in SPA | ❌ No | ✅ Yes |
| Protects against interception | ❌ Not fully | ✅ Yes |
| Requires server storage | Yes | No |
| Public client safe | ❌ | ✅ |
PKCE replaces the need for client secret in public clients.
Mental Model
Without PKCE:
Authorization Code = Bearer credential
With PKCE:
Authorization Code + Secret Proof Required
How It Fits Into Modern Architecture
Modern security stack:
OIDC Authentication
+ Authorization Code Flow
+ PKCE
+ JWT tokens
+ API validation
PKCE is now considered baseline security — not optional.
Summary
PKCE (Proof Key for Code Exchange) is an OAuth 2.0 extension that protects the Authorization Code flow from code interception attacks. The client generates a random code_verifier, derives a code_challenge (typically SHA256), sends the challenge during authorization, and later proves possession by sending the verifier during token exchange. The authorization server validates that the hashed verifier matches the original challenge. PKCE is mandatory for public clients and recommended for all OAuth/OIDC flows.