Skip to content

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

  1. App redirects user to IdP
  2. User logs in
  3. IdP redirects back with code
  4. App exchanges code for 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:

  1. Hashes the code_verifier
  2. Compares it with original code_challenge
  3. If match → issue tokens
  4. 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.