CodeOath
← All posts
Auth & Security60 min total · 19 parts

OAuth 2.0 and JWT Explained: How "Login with Google" Actually Works

Contents — Part 6 of 19: PKCE: Authorization Code Flow for Public Clients
Part 6 of 19 · ~2 min

PKCE: Authorization Code Flow for Public Clients

A public client — a single-page app, a mobile app, a desktop app — has no backend server to hold a client_secret; any secret embedded in client-side code or an app binary can be extracted by a sufficiently motivated attacker. Without some other protection, this reopens the exact vulnerability the client_secret was there to close: if an attacker intercepts the authorization code (a genuinely easier task on a mobile OS, where multiple apps can register the same custom URL scheme), nothing stops them from redeeming it themselves.

PKCE (Proof Key for Code Exchange, pronounced "pixie") fixes this without requiring a secret the client can't actually keep secret. The client proves it's the same party that started the flow, using a one-time value only it ever knew:

1. Client generates a random string: code_verifier (kept only in memory, never sent anywhere yet)
2. Client computes: code_challenge = BASE64URL(SHA256(code_verifier))
3. Client sends code_challenge (not the verifier) in the initial authorization request
4. Authorization server stores code_challenge, tied to the eventual authorization code
5. Client exchanges the code for a token, this time sending the original code_verifier
6. Authorization server hashes the received code_verifier and checks it matches the
   stored code_challenge — proving this request came from the same client that
   started the flow, without ever transmitting a static, reusable secret

An attacker who intercepts the authorization code from the redirect (step 4 of the main flow) still can't redeem it, because they never saw the original code_verifier — only its one-way hash passed over the network earlier. PKCE has become the default recommendation for every client type today, confidential or public, precisely because it costs nothing for a confidential client to also add and meaningfully strengthens security for public ones — modern OAuth guidance treats it as mandatory rather than public-client-only.