SBN

What is PKCE? Flow Examples and How It Works

PKCE (Proof Key for Code Exchange), pronounced “pixie,” is a security extension for OAuth 2.0’s Authorization Code flow. While it’s designed for scenarios where the client secret cannot be securely stored, all applications can benefit from PKCE. In fact, while it’s already recommended in the best practices for OAuth, PKCE is a requirement for all clients using the in-development OAuth 2.1 specification.

As an enhancement for standard OAuth, PKCE can benefit all types of applications for two big reasons:

  • Cross-Site Request Forgery (CSRF) attacks: When a malicious site or app initiates an authorization request without the user’s knowledge.

  • Authorization code interception/injection: When an attacker intercepts an authorization code and exchanges it for access tokens before the legitimate application does.

 In this guide, we’ll explore how PKCE stops these attacks. We’ll break down the standard Authorization Code flow, pinpoint where PKCE adds value, and examine why organizations are embracing it—even before it’s officially mandatory in the latest OAuth standard.

To better understand PKCE, you should be familiar with the OAuth standard. This beginner-friendly article should help!

Understanding the standard Authorization Code flow

OAuth provides several different “grant types”—standardized methods for obtaining access tokens. Grant types are associated with different scenarios, with each one offering a different balance of security and convenience. One of these is the Authorization Code grant type, widely considered to be the most versatile and secure of the bunch.

Authorization Code flow

The “vanilla” Authorization Code flow is meant for applications that can maintain a server-side component to securely store credentials. Here’s how it works:

  1. User initiates login: The user chooses to grant your application permissions via OAuth, such as by choosing “Log in with Service (e.g., Google)” in your app.

  2. Authorization Code request: The client requests an Authorization Code from the authorization server, including information about what the app is and what permissions it’s requesting.

  3. Prompt for consent: The authorization server asks the user to authenticate and provide consent for the app to access their resources.

  4. User authentication: The user logs in to the authorization server and approves the requested permissions.

  5. Authorization code return: The authorization server redirects the user back to your application with a temporary Authorization Code

  6. Token exchange: Your application sends this code to the authorization server along with your app credentials.

  7. Access token issued: The authorization server validates everything and returns ID and access tokens

  8. Resource access: Your application uses the access token to request protected resources

In this flow, the application never sees the user’s credentials. Instead, the user authenticates directly with the authorization server, which then provides the app with a temporary authorization code.

The security gap in standard Authorization Code flows

The standard Authorization Code flow has a fundamental flaw: there’s no way to verify that the client exchanging an authorization code for tokens is the same client that initiated the request. This raises several concerns:

  • An attacker who intercepts the authorization code can use it to obtain access tokens because there’s no verification that ties the code to the original requesting application.

  • Even if the application uses a client secret (essentially a password shared between the app and authorization server), this only proves the client’s identity, not that this specific client originally requested this specific authorization code.

  • Since there’s nothing binding the initial request to the token exchange, the flow is also vulnerable to CSRF attacks, in which a user could be tricked into initiating an unintended authorization flow. 

This security gap affects all types of applications, though it’s especially problematic for clients that can’t securely store client secrets. This is where PKCE comes in, binding the initial authorization request and the token exchange.

How PKCE reduces the Authorization Code attack surface

Without PKCE, OAuth authorization code flows don’t have a way to verify which specific client sent this specific request. To understand how PKCE eliminates this vulnerability, we merely need to look at its name: Proof Key for Code Exchange, meaning you need proof you originated the authorization request to exchange the code for tokens.

To achieve this, PKCE has the requesting application create a new type of secret, a “code verifier.” This is used to create a “code challenge,” which the authorization server uses to confirm which app sent the request. Here’s how it works: 

  1. User initiates login: Just like in a standard Authorization Code flow, the user initiates the process by selecting the prompt associated with granting your application permissions via OAuth, like “Log in with Service (e.g., Google).”

  2. Code verifier creation: Before starting the flow, your application generates a random secret. This is not the same as a client secret—it’s a special component called a “code verifier.” Client secrets can still be used alongside it. Your application also creates a “code challenge” by transforming the verifier, usually by hashing it (a one-way process that can’t be reversed or decoded).

  3. Authorization code request: The application requests an authorization code from the server and includes the code challenge (along with the hashing method, like SHA-256).

  4. Prompt for user consent: The authorization server prompts the user to authenticate and provide consent for the requested permissions (same as standard flow).

  5. User authentication: The user logs in and approves the permissions (same as standard flow).

  6. Authorization code return: The authorization server redirects back to your application with the code.

  7. Token exchange (with verification): Your application sends the code to the authorization server along with the original code verifier.

  8. Server verification: The authorization server compares the code verifier to the code challenge before issuing any tokens. For example, if the method used was hashing with SHA-256, the server will also hash the code challenge and ensure it matches the verifier; the two strings should be the same.

  9. Access token issued: If the code verifier matches up with the code challenge, the authorization server returns ID and access tokens.

  10. Resource access: Your application can now use these tokens to request the necessary resources (as in the standard flow).

Using PKCE in your endpoints enables the authorization server to verify that the client requesting tokens is the same one that made the request. Even if an attacker intercepts the authorization code, they can’t exchange it for tokens without the original code verifier. Only the legitimate application has this in its untransformed (e.g., unhashed) state.

PKCE in public vs. confidential clients

In OAuth terminology, clients are either “public” or “confidential” based on their ability to securely store credentials. Public clients are apps that cannot safely store a client secret because their code is fully exposed to the user or can be extracted easily. Public apps include: 

  • Single-page apps (SPAs) running entirely in the browser

  • Native mobile apps

  • Certain types of desktop apps, like those that don’t use TPMs (Trusted Platform Modules) to securely store credentials

Public clients need PKCE because they can’t rely on client secrets for security. Confidential clients, on the other hand, can securely store credentials because they either run in controlled server environments, or the code and secrets are otherwise inaccessible to end users. So, why would you want to use PKCE for a confidential client?

  1. Protection against authorization code injection attacks and CSRF: As previously mentioned, CSRF and authorization code injection attacks are potential threats to all types of applications.  Official OAuth 2.0 Best Practices recommend PKCE for confidential clients because it “provides strong protection against misuse and injection of authorization codes” and “prevents CSRF even in the presence of strong attackers.”

  2. Adjunctive security that complements (not replaces) client secrets: The OAuth PKCE spec makes no bones about it—PKCE is not a replacement for client secrets or authentication. While it was originally designed to protect public clients, PKCE proved useful as an add-on to existing mechanisms by preventing CSRF attacks, prompting the question: “Why not add PKCE if you can?”

  3. Protection against implementation vulnerabilities: In the absence of PKCE, it’s possible for a client implementation to fail at properly verifying state parameters (an OAuth mechanism used to deter CSRF attacks). Because PKCE is verified by the authorization server, it provides protection even when client-side verification is flawed or incomplete.

PKCE use cases and examples

Although PKCE was originally developed to secure authorization code flows on public clients, the reasons for adoption can vary across use cases and application types. Even so, there are virtually no scenarios in which an additional layer of seamless security is unwelcome.

Ideal PKCE use cases include:

  1. Native mobile applications: The original PKCE scenario, native mobile apps are public clients that can’t securely store secrets. Without PKCE, these applications couldn’t use the authorization code flow without exposing credentials to anyone with the knowledge and tools to find them.

  2. Single page applications (SPAs): SPAs benefit from PKCE in the same way native mobile apps do: all their code runs in the browser, meaning there’s no server-side storage for client secrets. SPAs rely on PKCE to use the authorization code flow safely.

  3. Desktop applications: Some desktop applications can leverage TPMs and other mechanisms to secure client secrets even if the application runs entirely on a user device—but many don’t have this design. Like other public apps, these desktop clients need PKCE to use the authorization code flow securely.

  4. OAuth 2.0 best practices and 2.1 compliance: OAuth 2.0 best practices recommend PKCE be used for every client, not just public ones. OAuth 2.1, despite being in its draft stage, has already been adopted by many organizations. It makes PKCE mandatory.

PKCE and MCP adoption

The Model Context Protocol (MCP) is a standardized way for Large Language Models (LLMs) and AI agents to connect with external tools, APIs, and data sources. The authorization specification for MCP formally adopted OAuth 2.1, requiring developers leveraging the protocol to “implement OAuth 2.1 with appropriate security measures for both confidential and public clients.” That means using PKCE.

Because MCP serves as the “universal remote” that serves up APIs to AI agents and LLMs, OAuth was the logical choice for authorization. OAuth already has a strong, proven foundation spanning over a decade, making developing a new standard just for AI use cases a moot point. OAuth 2.1 provides the latest and most secure set of requirements, and PKCE adds a crucial layer of protection to a budding ecosystem still finding its footing. 

MCP’s embrace of OAuth 2.1 (and thus PKCE) is particularly significant because of the AI protocol’s widespread acceptance and sudden rise to prominence. It’s already seen adoption by OpenAI’s Agents SDK and industry leaders like Google, with the tech giant releasing their own “complementary” Agent2Agent protocol that can work hand-in-hand with MCP. Looking to the future, as MCP becomes more prevalent, so does PKCE. 

No-hassle OAuth Authorization Code flows with PKCE

PKCE may sound like a complicated identity concept at first glance, but it’s easily integrated with the right tools. Descope is a comprehensive external identity and access management solution that makes complex auth challenges drag & drop simple.

Descope can be configured as an OIDC Provider to easily add PKCE-based flows to your app. Our MCP Auth SDKs help make remote MCP servers OAuth-compliant by implementing OAuth, PKCE, dynamic client registration and more in just three lines of code.

Sign up for a Free Forever account with Descope to see how PKCE-enhanced OAuth flows can enhance your users’ auth journey. Got questions about Descope? Book time with our auth experts.

*** This is a Security Bloggers Network syndicated blog from Descope Learning Center authored by Descope Learning Center. Read the original post at: https://www.descope.com/learn/post/pkce