Proof Key for Code Exchange Overview
On this page
The Proof Key for Code Exchange (PKCE) is an extension used in OAuth 2.0, to improve security for public clients. It provides an additional security layer on top of the authorization code grant flow, ensuring that the application that starts the authentication flow is the same one that finishes it. OAuth 2.1 incorporates PKCE and requires or recommends it for all OAuth clients running the code flow - confidential as well as public ones.
How Does PKCE Work?
Before diving into how PKCE works, it's important to first understand the authorization code grant flow and its associated security vulnerabilities.
In the Code Flow overview we explained:
- How this popular message exchange pattern works
- The kinds of client applications that use it
- Variations that can be used to get different types of tokens (e.g., ID Tokens and Refresh Tokens) and
- How the user is redirected back to the client application once authentication and delegation are complete.
This last point about OAuth URL redirection is an important one. As mentioned many times in the OAuth 2.0 Threat Model (RFC 6819), the use of redirects is one of the primary attack vectors of OAuth. There are many ways that an attacker may try to take advantage of the code flow's redirection back to the client. These security vulnerabilities are largely mitigated by:
- Requiring the client to authenticate itself at the token endpoint (when redeeming the authorization code)
- Using TLS on the client's redirect URI
- Having a unique redirect URI for each client which it has total control over.
For Web applications, these safeguards are usually in place. They may not be, however, for mobile applications. In such environments, an attacker may be able to obtain the client ID and secret used by the app when it authenticates itself to the OAuth server's token endpoint. Also, the redirection may use a custom scheme that is not confidential and may be possible for an attacker to also register to handle. This situation is shown in the following figure:
This vulnerability can be mitigated by using Proof Key for Code Exchange (PKCE), or "pixie" as it is commonly referred to, which is defined in RFC 7636.
Explanation of PKCE
The reason that such a vulnerability exists is because the OAuth server (i.e., the Curity Identity Server) doesn't know the difference between the legitimate app that starts the flow in step one and the malicious app that redeems the authorization code in step five. To close this gap the OAuth server needs a way of determining that the app that starts the flow is the same one that finishes it. This is what PKCE does. The way it accomplishes this is by specifying that:
- The legitimate client app creates a secret key that only it knows.
- The request in step one above includes a hash of this secret key and info about which hashing algorithm was used.
- Step five in the previous figure includes the actual key (coming from the legitimate app).
- Before the OAuth server returns the tokens in step six, it uses the secret key to determine if it can arrive at the same hash that was previously given (using the hashing algorithm provided earlier).
These additional steps are shown in the following figure:
This is the code flow using a proof key for the code exchange (hence the name). The key proves that the one who requested the code is the same entity that redeems it. This makes the authorization code a "proof of possession" token because the client must prove that it posses the secret key used when the authorization flow is initiated. Because the malicious app does not have access to the secret key, it will not be able to redeem the code. Conversely, the legitimate one does, so only it will receive tokens when redeeming the authorization code:
How to Implement PKCE (Pseudo Example Code)
To implement this, the client would use pseudocode such as the following when it starts the authorization request:
code_verifier = generate_random_string(length = 100) # (1)session["code_verifier"] = code_verifier # (2)hash = sha256_hash(code_verifier) # (3)code_challenge = base64_url_encode(hash) # (4)redirect_to_authorization_endpoint_using_pkce( # (5)code_challenge = code_challenge, code_challenge_method = "S256")
In (1)
, the client generates a random key or string of bytes that only it knows. It saves this in some persistent session store (2)
. It then hashes these bytes (3)
and encodes them for use in a URL (4)
. When redirecting to the OAuth server, it includes the hash of the key and the algorithm used to compute that hash (5)
.
Later, when the client receives the call back from the OAuth server, it would redeem the authorization code using pseudocode like this:
post_code_to_token_endpoint_using_pkce( # (6)code = request["code"], code_verifier = session["code_verifier"])
In this sample code, the client would include the key generated when it started the authorization request (as shown in step one in the figures above). Then, the Curity Identity Server would be able to verify that the client exchanging the code (step five in the previous diagram) is the original one. It would do this by using the signing algorithm included in (5)
and the code verifier in (6)
. If the result was the same as the hash in (3)
, then the request initiator and the code exchanger are the same entity, and tokens are issued; otherwise, an error results.
Example
For an actual code example of this, refer to the Python code sample on GitHub.
PKCE Benefits
Benefits for Confidential Clients
Confidential clients, such as server-side applications, are generally considered secure because they can store sensitive information such as client secrets. PKCE further strengthens their security by:
Preventing Authorization Code Interception: With PKCE, intercepted authorization codes are useless without the corresponding code verifier, blocking unauthorized usage.
Protecting Against CSRF Attacks: PKCE safeguards against CSRF by linking the authorization request directly to the client that initiated it.
Benefits for Public Clients
Public clients, such as mobile and single-page applications, are unable to securely store client secrets, which is why PKCE can be especially beneficial.
No Need for Client Secrets: PKCE removes the reliance on client secrets, which cannot be safely stored in public clients.
Protects Against Code Injection Attacks: Intercepted authorization codes cannot be used without the corresponding code verifier, blocking unauthorized access.
Improves Security for Native Applications: For mobile and desktop apps, PKCE prevents attackers from leveraging custom URL schemes to intercept authorization codes.
OAuth 2.0 Security Best Current Practice and PKCE
The OAuth 2.0 Security Best Current Practice, a document published by IETF as RFC 9126 addresses vulnerabilities in the original OAuth 2.0 specifications and suggests improved mechanisms to strengthen security. One of the key recommendations is to use PKCE for all OAuth clients, including confidential clients, to protect against code interception attacks. The use of the authorization code flow with PKCE is now suggested as the default method for all client types.
Other PKCE best practices include:
-
It is also suggested to use SHA-256 as the hashing algorithm used to compute the challenge and to disallow an unhashed challenge. Using PKCE is common with dynamically registered clients. To learn more about this, watch the Dynamic Client Registration video, which goes deeper into that protocol.
-
Another easy safeguard that you can use with the code flow and PKCE is Pairwise Pseudonymous IDs (PPIDs) which is covered in other tutorials.
Conclusion
PKCE is an auxiliary OAuth specification designed to close a security vulnerability that is common on mobile devices. It specifies the use of a proof key to ensure that the application that starts the code flow is the same one that finishes it. This challenge is very easy to implement as shown in the pseudocode of these tutorials. It is enabled by default in the Curity Identity Server and requiring it can be done with just a few clicks. Because it is so simple to use, it is recommended for all clients, mobile and otherwise, that are using the code flow. If you have questions about anything in these tutorials, you can also contact us. We're happy to help!
For further examples on how to implement PKCE in confidential and public clients, have a look at these articles:
Javascript SPA using Code Flow + PKCE Configuring Proof Key for Code Exchange Best Practices - OAuth for Single Page ApplicationsCurity
Join our Newsletter
Get the latest on identity management, API Security and authentication straight to your inbox.
Start Free Trial
Try the Curity Identity Server for Free. Get up and running in 10 minutes.
Start Free Trial