OAuth Security Engineering

PKCE, explained interactively

PKCE, pronounced "pixy", is the OAuth proof-of-possession extension that prevents a stolen authorization code from being redeemed by an attacker. This version adds security-training lenses for internal engineering enablement: the plain method trap, Authorization Code Injection against confidential clients, and the PKCE protection boundary where access-token defenses must take over.

Authorization Code Flow S256 by default Not a redirect URI replacement Not client authentication
Core mental model The authorization code is a locked box. The code_verifier is the key. The code_challenge is a commitment to that key sent before the box exists.

Authorization Code + PKCE flow

Click each block to inspect the exact data moving through the flow. The important security invariant: the client sends only the derived challenge up front, then proves knowledge of the original verifier at token exchange.

Live PKCE generator

Generate a valid verifier, derive the S256 challenge, and test how the authorization server verifies the token request.

Generate verifier and challenge

RFC 7636 defines the verifier as a high-entropy cryptographic random string using unreserved URI characters, 43 to 128 characters long. The S256 challenge is base64url(SHA256(ASCII(verifier))).

Waiting for verifier...
Generate a pair to see the computed challenge.

API shape

PKCE changes both the authorization request and token request. It does not replace redirect URI checks, client authentication for confidential clients, or state/nonce correlation.


            

            
Public vs confidential clients

Public clients prove possession using PKCE but cannot keep a client secret. Confidential clients still authenticate to the token endpoint, commonly with an Authorization Basic header, and should also use PKCE.

Verification lab

Paste a verifier into the simulated token request. The server recomputes the challenge and compares it with the challenge stored when the code was issued.

No verification attempted yet.

Attack simulator: authorization code interception

Walk through the exact scenario PKCE was designed for: the authorization code leaks through a redirect, log, malicious app link handler, browser extension, proxy, or referer-style bug.

Scenario controls

Run the flow with or without PKCE and observe whether the stolen authorization code can be exchanged for tokens.

Start the simulator.

Event log

What PKCE stops

  • Authorization code interception before the token request.
  • Malicious native app registering the same custom URL scheme.
  • Code leakage through logs or front-channel redirects, if the verifier remains secret.
  • Authorization-code injection against confidential clients when the injected code does not match the session-bound verifier.

What PKCE does not stop

  • Access token theft after token issuance.
  • Open redirect or weak redirect URI validation by itself.
  • XSS that steals the verifier before token exchange.
  • Bad account linking or missing ID token validation in OIDC login.

Security engineering takeaway

PKCE binds the authorization code to the client instance that started the flow. It is a code redemption control, not a full OAuth threat model by itself.

Training labs for engineering teams

Use these exercises to teach the implementation details that usually cause real PKCE mistakes in production systems.

1. The plain method trap

In plain mode, the value sent as code_challenge is the verifier itself. If an observer sees the authorization request and later obtains the authorization code, PKCE no longer adds a useful proof because the verifier-equivalent value was already exposed.

GET /authorize?
  response_type=code
  code_challenge=VERIFIER_VALUE
  code_challenge_method=plain

# Training question:
# What secret remains hidden from an observer?
Security policy: reject code_challenge_method=plain for normal internal clients. Treat it as a legacy exception that requires explicit risk acceptance.

2. Authorization Code Injection: confidential clients still need PKCE

A backend web application can authenticate to /token with a client secret, but that only proves which application is redeeming the code. It does not prove that this browser session initiated the authorization request that produced the code.

Attacker injects a stolen victim code into:
  https://client.example/callback?code=VICTIM_CODE&state=ATTACKER_STATE

Backend has valid client_secret, so without PKCE:
  client redeems VICTIM_CODE at /token
Teaching point: client authentication proves which application is calling /token. PKCE proves this specific client flow initiated the authorization request.

3. PKCE protection boundary

PKCE protects authorization-code redemption. After tokens are issued, token theft, replay, and misuse require separate controls.

  • Use least-privilege scopes and short access-token lifetimes.
  • Rotate refresh tokens and detect reuse.
  • Consider sender-constrained tokens such as DPoP or mTLS for high-risk APIs.
  • Protect browser/native storage from XSS, malware, logs, and backups.
Transition: after teaching PKCE, continue into access-token storage, refresh-token rotation, and sender-constrained token design.

How to use this in internal training

Prompt 1: plain downgrade

Show an authorization request with code_challenge_method=plain. Ask: what secret remains hidden if this URL is logged?

Expected control: enforce S256 and reject plain except documented legacy exceptions.

Prompt 2: injected code

Show a confidential-client callback containing a stolen victim code and an attacker session state. Ask: what does the client secret prove?

Expected control: bind the code to the session-owned verifier and reject token exchange when the proof does not match.

Prompt 3: stolen access token

After successful token issuance, assume the access token is stolen from browser storage. Ask: does PKCE still help?

Expected control: use short lifetimes, refresh rotation, safe storage, and sender-constrained tokens such as DPoP or mTLS for high-risk APIs.

Facilitator exercise

Pick a lab and ask engineers to identify the vulnerable line, the failed assumption, and the correct platform control.

Choose an exercise above.

PKCE implementation checklist

Use this as a review checklist for OAuth clients and authorization servers.

0% complete

Client requirements

Authorization server requirements

Precise terminology and edge cases

Security review notes to avoid common PKCE misunderstandings.

Cryptographic correctness checkpoints

Base64url encoding

Use URL-safe Base64: replace + with -, replace / with _, and remove = padding.

Verifier entropy

Generate the verifier with a CSPRNG such as crypto.getRandomValues. Do not use Math.random(), timestamps, counters, or reusable values.

Length invariant

The verifier must be 43 to 128 characters from the RFC 7636 unreserved character set. A raw 32-byte random value becomes 43 characters after base64url encoding.

PKCE terms

TermMeaning
code_verifierHigh-entropy secret generated by the client. It must be kept local until the token request.
code_challengeDerived value sent in the authorization request. With S256, it is not reversible back to the verifier.
code_challenge_methodUse S256. plain exists for compatibility but exposes a verifier-equivalent value in the front channel, so internal authorization servers should reject it unless a documented legacy exception exists.
Authorization codeShort-lived, single-use value issued after authorization. PKCE makes it useless without the verifier.

Common misconceptions

MisconceptionCorrection
PKCE replaces state.No. PKCE proves possession of the verifier for code redemption. state still helps correlate responses, restore UI state, and provide defense-in-depth against CSRF-style mixups.
PKCE replaces client secrets.No. Confidential clients should still authenticate to the token endpoint. PKCE is an additional binding of authorization code to client instance.
The challenge is a secret.No. The challenge is sent through the front channel. The verifier is the sensitive value.
PKCE fixes OAuth redirect bugs.No. Strict redirect URI validation, no open redirects, and secure callback pages are still required.

Mini quiz

Test the key distinction.

Question: An attacker steals the authorization code from the callback URL, but the legitimate client kept the verifier secret. What happens at the token endpoint?

Choose an answer.