The Six Steps of the OAuth 2.0 Authorization Code Flow with PKCE
Proof Key for Code Exchange (PKCE) secures the OAuth 2.0 authorization code flow by requiring the client to prove its identity with a dynamic, per-request secret. This step-by-step guide breaks down the six stages of generating, hashing, and verifying that secret to prevent code interception attacks.
By Wei Zhang
- Identity Standards Bodies
- Focuses on protocol security, deprecating vulnerable legacy flows, and enforcing cryptographic standards.
- Application Developers
- Focuses on practical implementation, library support, and reducing friction in mobile and single-page environments.
- Security Auditors
- Focuses on attack vectors, entropy validation, and ensuring cryptographic primitives are correctly applied.
Perspectives this story doesn't cover
- Legacy System Administrators
- End Users
Key points
- An OAuth 2.0 authorization server that supports the PKCE extension (RFC 7636).
- A client application capable of generating cryptographically secure random strings.
- A local library or native API capable of performing SHA-256 hashing and Base64URL encoding.
- A registered client ID and a secure redirect URI configured on the authorization server.
To execute the OAuth 2.0 Authorization Code Flow with PKCE, a client application must generate a random secret, hash it into a challenge, send that challenge to the authorization server, receive an authorization code after user consent, and finally present the original secret to exchange the code for an access token. This mechanism, formalized by the Internet Engineering Task Force (IETF) in 2015, ensures that an intercepted authorization code remains useless to an attacker. Before diving into the implementation, it helps to understand the vulnerability this extension patches. In a standard OAuth 2.0 flow, the authorization server redirects the user back to the application with a temporary authorization code. If a malicious application on the same device registers the same custom URI scheme, it can intercept that redirect and steal the code. Because public clients like mobile and single-page applications cannot safely store a static client secret, they historically had no way to prove they were the legitimate recipient during the token exchange.[1]
While security vendors often market their identity platforms as impenetrable 'zero-trust' solutions, the reality of OAuth 2.0 on public devices is far more pragmatic. The protocol was not originally built for environments where the application binary can be decompiled or where the browser environment is fully exposed. Proof Key for Code Exchange (PKCE) is not a magical AI-driven anomaly detector; it is a straightforward cryptographic patch to a known architectural gap. It solves the interception problem by introducing a dynamic, per-request secret. As Auth0 documentation explains, the extension 'binds an authorization request to the client that made it, so an intercepted code is worthless to an attacker.' The process breaks down into six distinct operational steps that any developer can implement using standard cryptographic libraries.[2]
The first step begins before the user even sees a login screen. The client application generates a cryptographically random string called the code verifier. According to IETF RFC 7636, this string must be between 43 and 128 characters long, utilizing a specific 66-character unreserved alphabet consisting of letters, digits, hyphens, periods, underscores, and tildes. In the second step, the client transforms the verifier into a code challenge. The application cannot send the raw verifier over the network during the initial request, as that would expose the secret to the very interception it aims to prevent. Instead, the IETF mandates using the SHA-256 cryptographic hash function to hash the verifier, followed by Base64URL encoding the 256-bit output. This specific hashing method is designated as 'S256' in the protocol.[1]
It is worth noting that the IETF specification technically allows a 'plain' challenge method, where the verifier is sent without hashing. However, this was only included as a fallback for constrained devices in 2015 that could not perform SHA-256 hashing. In modern implementations, using the plain method offers zero protection against interception and is widely rejected by major identity providers. With the S256 challenge prepared, the third step involves sending the authorization request. The client directs the user's browser to the authorization server's `/authorize` endpoint. This HTTP GET request includes standard OAuth 2.0 parameters like `client_id` and `redirect_uri`, but appends two crucial PKCE parameters: `code_challenge` and `code_challenge_method=S256`. The server stores these values alongside the session data.[1]
It is worth noting that the IETF specification technically allows a 'plain' challenge method, where the verifier is sent without hashing.
The fourth step hands control entirely to the authorization server for user authentication and consent. The user authenticates—typically via a username and password, passkey, or biometric prompt—and grants the requested permissions. As Okta Developer documentation notes, this phase remains entirely opaque to the client application, which simply waits for the server to complete its verification. In the fifth step, the client receives the authorization code. Once the user approves the request, the authorization server redirects the browser back to the client application's specified `redirect_uri`. Appended to this URL is a short-lived authorization code. In a compromised environment, this is the exact moment an attacker might intercept the redirect and capture the code, hoping to exchange it for an access token.
The sixth and final step is the token exchange, where PKCE proves its value. To retrieve the actual access token, the client makes a secure, server-to-server HTTP POST request to the `/token` endpoint. Crucially, the client includes the intercepted authorization code alongside the original, unhashed `code_verifier` generated in the first step. The authorization server then performs its own SHA-256 hash on the provided verifier and compares the result to the code challenge it stored during the third step. If the hashes match, the server knows the client requesting the token is the exact same client that initiated the login, and it issues the access token. If they do not match, the request is denied. 'Because it is so simple to use, it is recommended for all clients, mobile and otherwise, that are using the code flow,' notes identity provider Curity in its 2025 technical guidance.
The mathematical security of this mechanism relies entirely on the entropy of the code verifier. The IETF specification defines the verifier alphabet as 66 possible characters. By calculating the base-2 logarithm of 66 raised to the allowed string lengths, we find that a minimum-length 43-character verifier provides approximately 260 bits of cryptographic entropy. A maximum-length 128-character verifier yields 773 bits. Both of these values vastly exceed the 128-bit threshold generally considered secure against brute-force attacks by modern computing standards.[1][4]
Even if an attacker successfully captures the code challenge during the initial network request, reversing the SHA-256 hash to discover a 260-bit verifier is computationally infeasible with current technology. This robust mathematical foundation is why the upcoming OAuth 2.1 specification deprecates older, vulnerable flows entirely. Moving forward, the protocol makes PKCE mandatory for all public clients and highly recommended for confidential ones, as outlined by OAuth.com. By closing the interception loophole with a simple cryptographic handshake, PKCE ensures that delegated authorization remains secure even in the inherently hostile environments of modern mobile and single-page applications.[3]
Frequently asked
Does PKCE replace the need for a client secret?
For public clients like mobile apps, yes. For confidential clients like backend servers, PKCE is used alongside the client secret to prevent code injection attacks.
What happens if a platform does not support SHA-256?
The specification includes a 'plain' challenge method where the verifier is sent as-is, but this offers no protection against interception and is strongly discouraged.
Is PKCE required for all OAuth flows?
The upcoming OAuth 2.1 specification makes PKCE mandatory for all authorization code flows, deprecating older implicit flows entirely.
Why this matters
Without PKCE, mobile and single-page applications are vulnerable to authorization code interception, allowing attackers to hijack user accounts. Implementing this six-step cryptographic handshake ensures that even if a malicious app steals the login redirect, it cannot access the user's data.
Sources
[1]IETFIdentity Standards BodiesProof Key for Code Exchange by OAuth Public Clients
Read on IETF →
[2]Auth0Application DevelopersAuthorization Code Flow with Proof Key for Code Exchange (PKCE)
Read on Auth0 →
[3]OAuth.comSecurity AuditorsProof Key for Code Exchange
Read on OAuth.com →
[4]Factlen Editorial TeamSynthesis by Factlen editorial team
Read on Factlen Editorial Team →
Comments
More in Technology
See all →Web Security
How the Protocol-Host-Port Triple Defines the Security Boundary of a Web Document
9 sources
Console Hardware
Next-Gen Console Chips Tape Out, Xbox Helix Leaked at 56 TFLOPS vs. PS6 at 40
4 sources
Network Protocols
How the 6LoWPAN Adaptation Layer Squeezes 1280-Byte IPv6 Packets Into 127-Byte Smart Home Radios
7 sources
Digital Services Act
US Justice Department Intervenes in X's Appeal Against EU Digital Services Act Fine
7 sources
Every angle. Every day.
Get Technology stories with full source coverage and perspective breakdowns delivered to your inbox.




