In this article
September 11, 2025
September 11, 2025

What is PKCE and why every OAuth app should use it

A developer-friendly guide to Proof Key for Code Exchange (PKCE): how it works, the problems it solves, and why it’s essential for secure OAuth flows, regardless of the application type.

OAuth 2.0 introduced a flexible framework for delegated authorization, but over time, best practices have shifted as new attack vectors emerged. One of the most important evolutions was the introduction of Proof Key for Code Exchange (PKCE, pronounced “pixie”), an enhancement to the popular Authorization Code Flow, designed to secure public clients, such as single-page applications (SPAs) and mobile apps.

With the release of OAuth 2.1, PKCE is no longer just a best practice for public clients; it’s now mandatory for all applications, including confidential clients like server-based apps. This article revisits how PKCE works, why it’s so important, and what developers need to know when upgrading their OAuth implementations to align with OAuth 2.1.

What is PKCE?

The most popular OAuth 2.0 flow is the Authorization Code Grant, which allows applications to get tokens securely by exchanging an authorization_code. But this flow has a limitation: it relies on the client authenticating with a client_secret during the code exchange. That works for server-based (confidential) apps, which can safely store secrets, but not for public clients like single-page apps or mobile apps, which cannot keep secrets safe.

Native apps can be decompiled, and single-page apps have their entire source code available to the browser, so they cannot be trusted with secrets. Without additional protection, these apps are exposed to code interception attacks, where an attacker steals the authorization code and redeems it for an access token.

To solve this, OAuth 2.0 introduced the Proof Key for Code Exchange (PKCE) extension. PKCE secures the Authorization Code flow for public clients by adding three new parameters:

  • Code verifier: a cryptographically random string that the application creates when a user requests to log in.
  • Code challenge: a hash of the code verifier.
  • Code challenge method: the hashing algorithm used to generate the code challenge (typically SHA-256).

How PKCE works

Here’s how the PKCE flow works in practice:

  1. Generate a code verifier: When a user requests to log in, the app creates a cryptographically random string called the code_verifier. The app stores this value locally.
  2. Derive a code challenge: The app hashes the code verifier in order to produce the code_challenge. The hash is produced using the hashing algorithm specified by the code_challenge_method.
  3. Send the code challenge: During the request to the Authorization Server, the client includes the code_challenge and the code_challenge_method.
  4. Authenticate the user: The Authorization Server stores the code challenge and proceeds with user authentication. Once the user authenticates, the authorization server generates an authorization_code and returns it to the calling application.
  5. Exchange the code with the verifier: The application can now exchange the authorization code for an access token. To do so, it sends a request to the Authorization Server that includes the authorization_code and the code_verifier generated in the first step.
  6. Validation: The Authorization Server hashes the code_verifier, using the code_challenge_method it received in the 3rd step, and compares it to the stored code_challenge. If they match, an access token is issued; otherwise, an error occurs. The access token can then be used for secure API calls.

This effectively solves the code interception attack problem, as the malicious application would need not only the authorization_code but also the code_verifier to obtain an access token. Intercepting the code challenge won’t help either, since SHA-256 is a one-way hashing algorithm that cannot be decrypted.

Why use PKCE with server-side apps?

If server-side (confidential) apps can store a client secret, you might wonder: isn’t the Authorization Code flow already secure enough?

The short answer: PKCE adds protection against attacks that the client secret alone doesn’t cover.

  • Defense against code injection: Even in server-side apps, an attacker could attempt to inject or swap authorization codes during the redirect. With PKCE, the attacker would also need the original code_verifier to succeed, something they don’t have.
  • Safer in multi-device and proxy scenarios: Network intermediaries, compromised browsers, or malicious extensions can sometimes expose codes in ways that a client secret doesn’t protect against. PKCE ensures only the app that initiated the login can redeem the code.
  • Consistent, universal security model: By requiring PKCE everywhere, OAuth 2.1 eliminates confusion about when to use it. Developers no longer need to reason about whether their app is “public” or “confidential”. The guidance is simple: always use PKCE.
  • Future-proofing: As OAuth continues to evolve, flows without PKCE will be considered outdated. Adopting it now keeps your implementation aligned with the direction of the spec.

In other words, PKCE isn’t just a workaround for apps without secrets; it’s an additional safeguard that strengthens the Authorization Code flow for every client.

How to implement PKCE

If you’ve implemented PKCE before, the steps haven’t changed with OAuth 2.1. Here’s a simple example:

	
// Step 1: Generate a random code verifier
const codeVerifier = generateRandomString(128);

// Step 2: Create a code challenge
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await crypto.subtle.digest('SHA-256', data);
const codeChallenge = base64UrlEncode(digest);

// Step 3: Send code challenge in the auth request
const authUrl = `${AUTH_SERVER}/authorize?response_type=code
  &client_id=${CLIENT_ID}
  &redirect_uri=${REDIRECT_URI}
  &code_challenge=${codeChallenge}
  &code_challenge_method=S256`;

// Step 4: Exchange code + verifier for tokens
// Include `code_verifier` in the token request
	

Common pitfalls

Even with PKCE in place, there are a few common mistakes that can weaken security or break your implementation:

  • Reusing code verifiers: It’s important not to hard-code the code verifier and to generate a new one for each authorization request. If not, you are exposing yourself to replay attacks.
  • Using the plain method: OAuth 2.1 strongly recommends S256 as the only safe option. Don’t use plain as it doesn’t offer any protection.
  • Not storing the verifier securely during the flow: The client must keep the code_verifier until the code exchange is complete. Losing or overwriting it will cause the flow to fail.
  • Assuming PKCE is optional: In OAuth 2.1, it’s required for all clients. Don’t rely on old documentation that still says “PKCE is only for public clients.”
  • Inconsistent implementation across libraries: Some OAuth libraries implement PKCE automatically, while others require you to enable it explicitly. Make sure you understand how your SDK or framework handles PKCE so you don’t accidentally skip it.
  • Mixing PKCE with deprecated flows: PKCE only applies to the Authorization Code Grant. If you’re still using implicit or password grant flows, it’s time to migrate; OAuth 2.1 removes those for good reason.

OAuth 2.1 today: Draft, but ready to use

It’s worth noting that OAuth 2.1 is still officially a draft specification, and OAuth 2.0 servers aren’t going away any time soon. The ecosystem will continue to support OAuth 2.0 for years to come.

But here’s the important part: OAuth 2.1 doesn’t reinvent OAuth. Instead, it formalizes existing best practices and removes outdated or risky flows that security experts have already advised against for years. This means there’s no downside to adopting OAuth 2.1 guidance today, your implementation will still be compatible with current servers, while also being more secure by design.

If you’re starting a new project, you should follow OAuth 2.1 from the beginning. You’ll be protecting your users against known vulnerabilities, aligning with the future direction of the standard, and ensuring your application is secure from day one.

!!Want to dive deeper into what changed in OAuth 2.1 beyond PKCE? Check out our guide to OAuth 2.1 updates and how to migrate.!!

How WorkOS can help

WorkOS simplifies OAuth 2.1 adoption by providing a turnkey, enterprise-ready OAuth 2.0 platform that already aligns with modern security best practices. With built-in support for Authorization Code + PKCE, exact redirect URI enforcement (we only allow wildcards in staging envs), refresh token rotation, and secure token storage patterns, WorkOS helps you accelerate compliance while eliminating boilerplate.

Whether you're upgrading an existing app or building new integrations, WorkOS makes implementing secure, standards-compliant auth flows effortless, so you can focus on building great user experiences, not protocol minutiae.

Sign up with WorkOS today.

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.