OAuth 2.0 with PKCE Explained

PKCE hardens the OAuth 2.0 Authorization Code flow so a stolen authorization code can't be redeemed. The client proves it started the flow with a code_verifier. Here's how the pieces fit and the check that makes it secure.

Security Engineeroauth2pkceauth

Why PKCE exists

In the plain Authorization Code flow, an attacker who intercepts the one-time code (via a redirect leak, a malicious app, etc.) can exchange it for tokens. PKCE (Proof Key for Code Exchange) fixes that: the client proves it's the same party that started the flow. It's now the standard for public clients (SPAs, mobile) - and replaces the deprecated implicit grant.

The flow

  1. Client generates a random code_verifier and its code_challenge = base64url(SHA256(verifier)).
  2. /authorize: client sends the challenge (+ code_challenge_method=S256). The server stores it with the issued code.
  3. /token: client exchanges the code plus the raw code_verifier.
  4. Server verifies: SHA256(verifier) must equal the stored challenge. Match -> issue tokens. Mismatch -> reject.

A stolen code is useless without the matching verifier, which never left the client.

The check that makes it secure

This is the step that's easy to skip (and fatal to skip) - the token endpoint must verify the verifier before issuing a token:

import hashlib, base64

def verify_pkce(code_verifier: str, stored_challenge: str) -> bool:
    digest = hashlib.sha256(code_verifier.encode()).digest()
    computed = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()  # base64url, no padding
    return computed == stored_challenge

# in /token:
if not verify_pkce(request.form["code_verifier"], entry["code_challenge"]):
    return jsonify({"error": "invalid_grant"}), 400
# only now issue the access token

The details that bite people

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

What is PKCE in OAuth 2.0?

Proof Key for Code Exchange - the client generates a secret code_verifier and sends its hash (code_challenge) when starting the flow, then sends the raw verifier when exchanging the code. The server checks they match, so a stolen authorization code can't be redeemed.

Why use PKCE instead of the implicit grant?

The implicit grant returns tokens directly in the redirect (exposed in URLs/history) and is deprecated. Auth Code + PKCE keeps tokens out of the front channel and protects the code from interception, which is why it's the standard for SPAs and mobile apps.

How does the server verify PKCE?

At the token endpoint it computes base64url(SHA256(code_verifier)) and compares it to the code_challenge stored at /authorize. If they don't match it rejects the exchange with invalid_grant. Use the S256 method, not plain.

What is the PKCE flow in OAuth?

PKCE (Proof Key for Code Exchange) hardens the authorization-code flow: the client sends a hashed code_challenge up front and the original code_verifier when exchanging the code. A stolen auth code is useless without the verifier, so it is safe for mobile apps and SPAs.

Keep learning

Close a SQL Injection in a Search EndpointSecurity projectNeutralize a Stored XSS in CommentsSecurity projectRemove Hardcoded Credentials From Source CodeSecurity projectSecurity roadmapStep by step to hiredSecurity interview questionsSTAR answersAll Security projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →