Stopping Code Interception: Implementing OAuth 2.0 PKCE
Learn how OAuth 2.0 PKCE prevents authorization code injection attacks in public clients like mobile apps and SPAs by using dynamic cryptographic verifiers.
09 Oct 2025, 13:28 UTC

The Vulnerability of the Authorization Code
In a standard OAuth 2.0 Authorization Code flow, the authorization server sends a code back to the client via a redirect URI. For public clients—such as mobile apps or Single Page Applications (SPAs)—this code is exposed to the operating system or the browser history. If a malicious application on a mobile device intercepts this code, it can potentially exchange it for an access token, gaining unauthorized access to the user’s data.
The solution is Proof Key for Code Exchange (PKCE), pronounced "pixie". PKCE ensures that the entity requesting the token is the same entity that initiated the authorization request, effectively neutralizing intercepted codes.
How PKCE Secures the Exchange
PKCE replaces the static client_secret (which cannot be safely stored in public clients) with a dynamic, one‑time secret created for every single request. This process involves three components:
- Code Verifier: A cryptographically random string generated by the client.
- Code Challenge: A transformed version of the verifier (usually a SHA‑256 hash).
- Challenge Method: The algorithm used to transform the verifier (typically
S256).
The client sends the code_challenge during the first leg of the flow. The server stores this challenge. When the client later exchanges the code for a token, it must send the original code_verifier. The server hashes the verifier and compares it to the stored challenge; if they match, the token is issued.
Worked Example: The PKCE Handshake
Assume a client is using the S256 method. The following sequence occurs on the client side and the authorization server.
1. Client‑Side Generation
The client generates a random string and hashes it. This happens locally before any network requests are made.
# Example Code Verifier (random string)
code_verifier = "dBjftGZpSdM3S6uS9S7XmS8V2uL9fP1zQ4rT7wY0xN"
# Example Code Challenge (Base64Url encoded SHA-256 hash of the verifier)
# SHA256("dBjftGZpSdM3S6uS9S7XmS8V2uL9fP1zQ4rT7wY0xN") -> Base64Url
code_challenge = "E9MelhoS_S-S7XmS8V2uL9fP1zQ4rT7wY0xN_example"
2. The Authorization Request
The client directs the user to the /authorize endpoint. This request is run in the user’s browser.
GET /authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
scope=openid%20profile&
code_challenge=E9MelhoS_S-S7XmS8V2uL9fP1zQ4rT7wY0xN_example&
code_challenge_method=S256
3. The Token Exchange
After receiving the code, the client makes a POST request to the /token endpoint. This requires POST permissions to the server’s token API.
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTHORIZATION_CODE_RECEIVED&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
code_verifier=dBjftGZpSdM3S6uS9S7XmS8V2uL9fP1zQ4rT7wY0xN
Implementation Limitations and Risks
While PKCE significantly hardens the flow, it is not a silver bullet. It does not replace the requirement for TLS (Transport Layer Security); tokens are still transmitted over the wire and must be encrypted in transit. Additionally, the security of PKCE relies entirely on the quality of the code_verifier. If a developer uses a predictable string or a weak random number generator, an attacker could potentially guess the verifier.
Furthermore, ensure your Authorization Server supports the S256 method. While the specification allows for a plain method (where the challenge equals the verifier), plain provides no actual security against interception and should be avoided.
Verifying Your Implementation
To confirm PKCE is working correctly, you can use a network proxy (like Burp Suite or OWASP ZAP) to inspect the traffic:
- Check the Authorize Call: Verify that
code_challengeandcode_challenge_method=S256are present in the query parameters. - Check the Token Call: Verify that
code_verifieris sent in the request body. - Negative Test: Attempt to exchange a captured
codeusing a differentcode_verifier. The server must return aninvalid_granterror.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.