July 25, 2026
PKCE-enhanced Authorization Code Flow | OAuth Explained
Overview

By David Mosyan
7 min read
Overview
Before diving into the Proof Key for Code Exchange (pronounced "pixy"), we have to understand how the OAuth flow works. OAuth (Open Authorization) is a protocol that allows third-party applications to access user data on another service without exposing users' credentials.
Let's discuss a simple example. Suppose you are using a third-party scheduling application that automatically creates Google Calendar events for you. To do its job, the app needs permission to access your calendar. What you definitely don't want to do is hand over your Google username and password to a third-party app.
This is exactly where OAuth comes into play. Instead of asking for your credentials, the app redirects you to Google's secure login page. There, Google asks you to grant the app a specific "scope" (a limited permission). In this case, just the ability to create calendar events. The app won't be able to read your emails, delete your photos, or see other calendar entries. Once you approve this request, Google hands a secure access token (more on that later) back to the app. This token acts like a temporary keycard, allowing the app to do only one thing (create a calendar event).
OAuth terminology
Now that we have a high-level overview, the next step is to unpack the flow and see what happens behind the scenes. But before that, let's understand the terminology. OAuth defines 4 main roles involved in any flow (there are different OAuth flows, which we will discuss later):
Resource Owner: the entity granting access to resources (typically a user). In the example below, it's you who owns the Google Calendar.
Resource Server: the entity hosting the protected resources, typically a backend API. In this case, it's the Google Calendar API.
Client: the application calling an API with an access token. It's the third-party app that will create calendar events.
Authorization server: the entity that authenticates the resource owner and issues access tokens to the client. The Google server is the one that checks your username and password first, and then generates the access token.
To summarize, the Resource Owner (user) interacts with a Client (third-party app) to access protected resources at a resource server (Calendar). To do so, the Authorization server generates an access token (with a scope) and hands it to the Client.
Access token
The result of the OAuth flow is the access token generated by the Authorization server for the client. 2 types of access tokens exist: opaque and structured. Opaque access tokens are random string identifiers that serve as a "reference" to session or user data stored securely on an authorization server. Because they're random strings, neither the client nor the resource server can decode them to see the permissions/scope attached to the token. Only the authorization server that issued the token can. The client sends the token to the resource server, which then sends it to the authorization server to check the permissions.
Structured tokens, on the other hand, can be decoded. The authorization server always includes a digital signature in structured tokens to protect their integrity. The most mainstream example is the JSON Web Token (JWT) format. In fact, you can go to https://www.jwt.io/ website, paste the JSON Web Token, and inspect the structure. You can check out this article for more information about JSON Web Tokens.
The important thing here is that Access tokens are not for authentication. It cannot tell if the user has authenticated. If a malicius user stoles an access token it can use it with any other client to access the data. The authorization server will NOT ask to authenticate again.
Authorization Flows
There are different paths for a client to get an access token. These paths are called flows. OAuth defines 4 flows to get an access token and they are called grant types. Choosing the correct flow is not covered here, but on a high level, it depends on the application type, the level of trust, the user experience, etc.
To understand the PKCE enhanced authorization code flow, let's quickly discuss a couple of other grant types.
Implicit flow
This flow was designed to work around browser limitations that prevented cross-origin requests to the authorization server endpoints. Previously, those limitations prevented browser applications from implementing the code flow (more on that later) and they had to fall back to the implicit flow.
The problem is that the tokens are part of the URL, which is a security weakness as they may leak via the browser history, via server logs and for other reasons. With this flow, the authorization server returns tokens directly to the redirect URI (more on this later). OAuth now discourages the use of the implicit grant for obtaining access tokens in SPAs (single-page applications). Don't confuse this flow with the more secure Implicit Flow With Form Post.
Password flow
In this flow, the client uses the user's password to obtain the token from the authorization server. As you already guessed, this goes against the principle of OAuth. Since the flow uses username and password, you can not implement any other authentication methods, which limits the security capabilities of an application.
Again, the specification urges minimizing the use of this flow.
Client credentials flow
In this flow, the client uses client_id and client_secret to get the access token. The flow consists only of a single request to the authorization server token endpoint. You should use the client credentials flow only in calls that do not involve an end user, as the received access token doesn't contain any information about the user. This flow is used for machine-to-machine communication.
If a user logs in to a client that calls an API that calls another API, the first API should share a user-level access token with the second API instead of retrieving a new token with the client credentials flow. This allows for keeping the user identity and other attributes verifiable and auditable throughout the call chain.
Authorization code flow
This is the most widely used flow, often referred to as code flow. When implementing user-facing applications (web, desktop, mobile, console) the code flow is a standard solution to implement OAuth. It includes multiple steps:
Client initiates the flow on behalf of the user. The authorization server returns a so-called authorization code that represents the user's grant. That code is sent back to the authorization server to get the access token. Let's break this down.
When the client initiates the flow to the authorization server, it doesn't return the code immediately. Just like in the example of the Google Calendar scheduler app, the authorization server redirects users to the authentication page. The important part is that the user doesn't share the credentials (can be login/password, or any other authentication resource) with the client (that's what the password flow does); it passes the authentication with the authorization server directly.
OAuth doesn't define how authentication works; the application can authenticate users in many different ways with this flow. Once the authentication is done, the user is presented with the consent screen (see the example below).
This allows users to see and approve the permissions the 3rd-party application needs to do its job. Once the user completes authentication and the consent steps, the client receives the authorization code. It's a short-lived, one-time-use string (usually valid for a minute). The client then sends this code to the authorization server again to receive the Access Token back.
Securing Client
Now let's add another layer of security to the existing flow. How can the authorization server be sure that it forwards the authorization code to the correct client? This is where the redirect URI comes into play. It's created when registering the client with the authorization server. It's a setting that the 3rd party application (calendar scheduler) configured to talk with the Authorization Server (Google Server). This is where the client expects to receive the authorization code.
The example HTTP GET request, among other things, includes response_type=code which informs the authorization server to return the authorization code to the configured redirect URI of the client. It also includes client_id (identifier of the client).
One more important parameter is statewhich is a random, unguessable value that is unique for every authorization request. Once the user completes the authentication process, the authorization server sends the authorization code to the redirect URI along with the same state value. This helps the client to mitigate certain attacks and make sure that the response came from the expected authorization server. So the client checks if the received state parameter matches the one it sent initially.
GET /o/oauth2/v2/auth?
response_type=code&
client_id=SCHEDULER_APP_CLIENT_ID&
redirect_uri=https%3A%2F%2Fappscheduler.com%2Foauth%2Fcallback&
scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.events&
state=xyz123_random_anti_csrf_token
HTTP/1.1
Host: accounts.google.comGET /o/oauth2/v2/auth?
response_type=code&
client_id=SCHEDULER_APP_CLIENT_ID&
redirect_uri=https%3A%2F%2Fappscheduler.com%2Foauth%2Fcallback&
scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.events&
state=xyz123_random_anti_csrf_token
HTTP/1.1
Host: accounts.google.comSummary of the parameters:
response_type=code: Tells Google to return a temporary authorization code.redirect_uri: The strict URI pre-registered by the scheduler app.scope: Limits permission specifically to creating/managing calendar events.state: The unguessable token stored in the user's session to prevent CSRF attacks.
We added another security layer to the client side. What about the server side? As we discussed, the authorization server expects the authorization code to be sent again, and once it verifies that the code matches the one it generated, the server returns the access token. But how can it be sure that no one stole the authorization code from the client and is trying to get the access token first?
Proof Key for Code Exchange (PKCE)
We kind of know already what this does, but to clarify what problem it solves, we can think about this as an assurance for the authorization server that a malicious party did not intercept an authorization code and send it to fetch tokens. If it happened, it's called an authorization code injection attack.
If the flow is enhanced with PKCE, the server verifies that the token request is part of the same flow by binding the authorization code to a challenge. The client generates a random runtime secret called code_verifier and binds it to the current session. The client calculates a hash over the code verifier (known as code_challenge parameter) and adds the code_challenge and the hash method in the authorization request. It doesn't add the actual value/code_verifier; it adds the Hash value and the hashing algorithms.
Let's add this into our example requests.
GET /o/oauth2/v2/auth?
response_type=code&
client_id=SCHEDULER_APP_CLIENT_ID&
redirect_uri=https%3A%2F%2Fappscheduler.com%2Foauth%2Fcallback&
scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.events&
state=xyz123_random_anti_csrf_token&
code_challenge=E9Melhoa2OwvFrGMTJguCH5RTK6YWjTOdC-59f_2Kmg&
code_challenge_method=S256
HTTP/1.1
Host: accounts.google.comGET /o/oauth2/v2/auth?
response_type=code&
client_id=SCHEDULER_APP_CLIENT_ID&
redirect_uri=https%3A%2F%2Fappscheduler.com%2Foauth%2Fcallback&
scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.events&
state=xyz123_random_anti_csrf_token&
code_challenge=E9Melhoa2OwvFrGMTJguCH5RTK6YWjTOdC-59f_2Kmg&
code_challenge_method=S256
HTTP/1.1
Host: accounts.google.comThe authorization server takes the code_challenge and code_challenge_method and binds it to the existing flow. Later, the client adds the code verifier value and sends it along with the authorization code. The server now can use the calculated method (code_challenge_method) and do the calculation (hashing). Because only the party that initiated the flow can send a correct code verifier in the final token request, the server ensures that the client that initiated the OAuth flow is the party ending it. Meaning the tokens are sent to the correct client at the end.
๐ and subscribe for more articles! ๐