Updated on 13 May, 202615 mins read 11 views

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 Token

Example:

  1. User clicks “Login with Google”
  2. Apps redirects user to authorization server
  3. User authenticates
  4. Authorization server redirects back with:

    ?code=abc123
  5. Client exchanges abc123 for 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 token

This 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 apps

PCKE solves this problem without needing a client secret.

Code Idea Behind PKCE

PKCE introduces:

  1. code_verifier
  2. 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 issued

PKCE Components

1 code_verifier

A cryptographically random string.

Example:

dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Requirements:

  • High entropy
  • Random
  • 43-128 characters
  • URL safe

2 code_challenge

Derived from the verifier.

Usually:

BASE64URL(SHA256(code_verifier))

Example:

E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM

3 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:

abcxyz123securelongrandomvalue

Step 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=S256

Important:

  • 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_challenge

Step 5: Authorization Code Returned

Server redirects:

https://app.com/callback?code=AUTH_CODE

Step 6: Token Exchange

Client sends:

POST /token

grant_type=authorization_code
code=AUTH_CODE
code_verifier=original_random_secret

Step 7: Server Verification

Authorization server:

SHA256(code_verifier)
↓
BASE64URL encode
↓
Compare with original code_challenge

If match:

  • Access token issued

If not:

  • Request rejected

Why PKCE Works

Suppose attacker intercepts:

authorization_code

But attacker does NOT know:

code_verifier

Without verifier:

  • Token exchange fails

Thus intercepted code bceomes useless.

 

Buy Me A Coffee

Leave a comment

Your email address will not be published. Required fields are marked *