What is Proof Key for Code Exchange (PKCE)? A Practical Guide
.webp)
Key takeaways
- PKCE is an OAuth 2.0 extension that binds an authorization request to the client that made it, so an intercepted code is worthless to an attacker.
- It was built for public clients like mobile and single-page apps that cannot safely store a client secret.
- The flow adds three parameters, a code verifier, a code challenge, and a challenge method, with S256 as the recommended method.
- OAuth 2.1 makes PKCE the baseline, requiring it for public clients and recommending it for all clients.
- Confidential clients still benefit, mainly through protection against authorization code injection.
- Enforcement, fresh verifiers, and S256 matter most, since skipping them invites downgrade attacks and weakens the protection.
Every time you tap "Log in with..." on a mobile app, a short-lived authorization code makes a quick round trip between the app and the login server. It feels instant. In that blink, though, the code travels across networks, redirects, and sometimes devices that many people share. For years, attackers found ways to grab that code in transit and trade it for real access.
The stakes are not small. According to Verizon's 2025 Data Breach Investigations Report, credential abuse remains the single most common way attackers gain their first foothold, factoring into roughly one in five breaches. When one intercepted code can unlock an account, closing that gap matters, especially on shared workstations where a single device serves many people. That gap is exactly what Proof Key for Code Exchange (PKCE) closes. PKCE is a security extension to OAuth 2.0 that binds an authorization request to the app that made it, so an intercepted code cannot be exchanged for tokens by anyone else. It does not authenticate users or replace MFA, but ensures that only the application that initiated the authorization request can redeem the returned authorization code. This guide explains what PKCE is, how it works step by step, where it fits across client types and shared devices, and the best practices and pitfalls to know about.
What is PKCE?
PKCE, pronounced "pixie," stands for Proof Key for Code Exchange. It extends the OAuth 2.0 authorization code flow with a per-request secret that only the legitimate client knows. The name captures the idea: The app must present proof that it started the flow before it can exchange the code for tokens. Originally built for mobile and single-page apps that cannot safely store a client secret, the extension now anchors OAuth 2.1, which requires it for public clients and recommends it for every client running the code flow.
It's important to distinguish PKCE from authentication itself. PKCE does not verify a user's identity or determine whether they should be granted access. Instead, it protects the OAuth authorization code exchange from interception and misuse. Authentication methods such as passwords, passkeys, biometrics, or MFA still establish who the user is.
OAuth vs PKCE
OAuth defines the flow; PKCE hardens one step of it. Here's the split:
The Problem PKCE Solves
The standard authorization code flow works well when an app has a backend that can hold a client secret. Public clients break that assumption. A native app can be decompiled to reveal a hardcoded secret, and a single-page app exposes its entire source to the browser. A mobile app also often receives its redirect through a custom URL scheme that a malicious app can register too.
This creates a verification gap. The authorization server cannot tell the legitimate app that started the flow apart from a malicious one that intercepts the code and redeems it. Anyone who captures the code, whether over open Wi-Fi, through a leaked log, or on a shared device, can trade it for tokens. PKCE removes that gap.
How PKCE works
PKCE adds three parameters to the flow: a code verifier, a code challenge, and a challenge method. Here is the quick flow:
- The client generates a random code verifier.
- The client hashes it into a code challenge using S256.
- The authorization request sends only the challenge.
- The authorization server stores the challenge and issues an authorization code.
- The client submits the original verifier during token exchange.
- The server hashes the verifier and compares it to the stored challenge.
- If both values match, tokens are issued.
The code verifier
When a user starts logging in, the client generates a code verifier, a cryptographically random string between 43 and 128 characters. The client holds onto this value and never includes it in the authorization request.
The code challenge and S256
The client derives a code challenge by hashing the verifier with SHA-256 and base64url encoding the result. This challenge method, labeled S256, travels with the initial authorization request. The plain method sends the verifier unchanged, so S256 remains the safer choice because an intercepted challenge reveals nothing usable.
The token exchange and server validation
The authorization server stores the challenge and returns an authorization code as usual. When the client exchanges that code for tokens, it includes the original verifier over the secure back channel. The server hashes the verifier again and compares it to the stored challenge. A match means the server issues tokens, while a mismatch or a missing verifier ends the request. An attacker who intercepts only the code cannot complete this step, because the verifier was never exposed in the authorization request.
PKCE vs No PKCE: A Side-by-Side Comparison
The comparison highlights an important distinction: PKCE doesn't encrypt authorization codes or stop them from being intercepted. It removes their value instead. Only the client holding the original code verifier can exchange a code for tokens, so a stolen code gets an attacker nothing.
PKCE for Public vs Confidential Clients
Public clients gain the most, since they run OAuth securely without storing a secret they cannot protect. Confidential clients, the server-side apps that can hold a secret, still benefit. PKCE defends against authorization code injection, where an attacker swaps in a stolen code during the redirect, and it adds protection in proxy or multi-device situations that a client secret alone leaves exposed. This is why the OAuth Security Best Current Practice recommends PKCE for every client, and why OAuth 2.1 folds it into the baseline.
Is PKCE Part of OAuth?
Yes. PKCE is an extension to OAuth 2.0 (RFC 7636) and has become a baseline security requirement in OAuth 2.1. Rather than replacing OAuth, it strengthens the Authorization Code Flow by proving that the same client initiating the request is also redeeming the authorization code.
PKCE on shared and frontline devices
Most PKCE explanations assume one person per device. Frontline settings rarely work that way. On a manufacturing line, a warehouse floor, or a retail counter, many workers rotate through the same handheld scanner, kiosk, or HMI terminal across a single shift. Each login and handoff becomes another moment where a code could leak, and secrets stored on a shared endpoint are even harder to protect than on a personal phone.
However, PKCE addresses only one part of the security problem. It protects the authorization code during the OAuth flow, but it does not identify which worker is using a shared terminal, enforce session ownership, or manage secure user switching. Organizations still need authentication methods that reliably associate every session with the correct individual.
PKCE suits these environments well because it removes the need for a shared static secret and ties each authorization to a fresh, single-use verifier. For shared-device operations, this pairs naturally with passwordless methods like badge tap or proximity login.
PKCE complements passwordless authentication on shared devices. While PKCE ensures intercepted authorization codes cannot be reused, OLOID ensures each authentication session is tied to a verified worker through passwordless methods such as badge tap, biometrics, or proximity login. Together, they provide both secure OAuth authorization and accurate user attribution on shared endpoints.
PKCE Best Practices
- Choose S256 over plain, and reject unhashed challenges on the server.
- Generate a fresh, high-entropy verifier for every authorization request.
- Enforce PKCE on the authorization server rather than trusting clients to send it.
- Keep client authentication where you have it, since PKCE adds a layer rather than replacing secrets.
- Pair PKCE with HTTPS everywhere and short authorization code lifetimes.
Common PKCE Pitfalls
Teams still trip over a few things. Downgrade attacks are the classic one: When a server supports PKCE but does not require it, an attacker can strip the PKCE parameters from the request and force a weaker flow, so enforcement matters as much as support. Storing the verifier carelessly, for example, where other code on the device can read it, reopens the door the verifier was meant to close. Misconfiguration, such as accepting the plain method, quietly weakens protection. Finally, treating PKCE as a complete security program invites trouble, since it guards the code exchange and leaves other controls to do their own jobs.
Bringing PKCE to Real Workplaces
PKCE has moved from a mobile-specific fix to a baseline for modern OAuth. It closes the interception gap cheaply, works for public and confidential clients alike, and now sits at the core of OAuth 2.1, which makes it mandatory for every authorization code flow rather than an optional hardening step. Building on it today isn't a bet on a niche technique; it's aligning with where the standard is already headed.
The payoff is sharpest in the environments legacy OAuth handled worst: shared devices. On a terminal that cycles through a dozen workers a shift, an intercepted authorization code is far more exposed than it would be on a single user's phone. PKCE binds that code to the client instance that requested it, so a code lifted mid-flow is useless to anyone else, however many people touch the same device. It also spares teams from distributing and rotating a client secret across a whole fleet of terminals, which is its own quiet operational burden at the frontline scale. For operational settings where shared devices are the norm, from logistics to critical infrastructure, that mix of flow security and low overhead is a real advantage. It lets teams building frontline access, OLOID among them, deliver fast passwordless sign-in on shared terminals while keeping every authorization bound to the client that earned it.
FAQs
1. Is PKCE mandatory now?
Yes, for public clients under OAuth 2.1, which requires it for the authorization code flow and recommends it for confidential clients too. Many providers already enforce it by default.
2. What is the difference between the code verifier and the code challenge?
The verifier is the random secret the client keeps. The challenge is a SHA-256 hash of that verifier, sent in the first request. The server later confirms the verifier matches the challenge.
3. Does PKCE replace the client secret?
No. PKCE adds a per-request layer. Public clients use it in place of a secret they cannot protect, while confidential clients should keep their secret and run PKCE on top.
4. What is the difference between plain and S256?
Plain sends the verifier unchanged, while S256 sends a SHA-256 hash of it. S256 is recommended because an intercepted challenge reveals nothing usable.
5. Does PKCE protect against CSRF?
It helps by binding the request to the token exchange, which blocks authorization code injection. You should still use the state parameter for full CSRF protection.
6. Does PKCE replace MFA or user authentication?
No. PKCE protects the OAuth authorization code exchange, not user authentication. Organizations should continue using authentication methods such as passwords, passkeys, biometrics, or multi-factor authentication to verify user identity. PKCE simply ensures that intercepted authorization codes cannot be redeemed by another client.



Get the latest updates! Subscribe now!
