Pixy Dust For Web Applications
PKCE, which stands for “Proof of Key Code Exchange”, and is pronounced “pixy,” is an extension of the OAuth 2.0 protocol that provides an additional security layer helping to prevent intercept attacks.
Articles about OpenZiti often discuss application and network security, and this article will follow that trend.
Here I discuss how PKCE enhances the security of authorization code grant flows used in web applications. I will explore some benefits, best practices, and challenges of working with PKCE.
Perhaps best of all, I will also highlight how OpenZiti BrowZer automatically brings you the power of PKCE... and how BrowZer facilitates an easy way to put a web-based cloak of invisibility around your application, further protecting it from malicious threat actors.
Auth code grant flow security vulnerabilities
Before we discuss how PKCE works, let's first describe vulnerabilities that can be exploited in the authorization code grant flow.
When an authorization request is made, the authorization code grant type requires the authorization server (your preferred IDP) to generate an authorization code, which is returned to the client application via a redirect URL. This code can then be exchanged for an access token, which can be used to access the user’s data.
An attacker can potentially intercept the authorization code that is sent back to the client and exchange it for an access token, which can cause serious data leaks or breaches.
One popular method malicious actors use to intercept authorization codes is by registering a malicious application on the user’s device (e.g. a browser extension). This malicious application will register and use the same custom URL scheme as the client application, allowing it to intercept redirect URLs on that URL scheme and extract the authorization code:
How does PKCE work?
PKCE addresses the above vulnerability by introducing a new flow and three new parameters:
code verifier
code challenge
code challenge method
Before an authorization request is made, the client creates and stores a secret called the code verifier
. The code verifier
is a cryptographically random string that the client uses to identify itself when exchanging an authorization code for an access token. It has a minimum length of 43 characters and a maximum length of 128 characters.
The client also generates a code challenge
, which is a transformation of the code verifier
. The code challenge
is sent with the initial authorization request, along with a code challenge method
. The code challenge method
is the transformation mode used to generate the code challenge
.
There are two code challenge methods that PKCE supports: plain
and S256
.
plain
:
Inplain
mode, the code challenge is equal to the code verifier; nothing changes.S256
:
InS256
mode, the SHA-256 hash of the code verifier is encoded using the BASE64URL encoding. TheS256
method is recommended by the specification and much preferred over theplain
method.
Next, the code challenge
is securely stored by the IDP, and an authorization code is returned with the redirect URL as usual.
When the client wants to exchange this authorization code for the access token, it sends a request that includes the initial code verifier
. The server then hashes the code verifier
using SHA-256 (if it supports acode challenge methodS256
) and encodes the hashed value as a BASE64URL. The corresponding value is then compared to the code challenge
. If they match, an access token is issued. Otherwise, an error message is returned.
This flow ensures that a malicious third party cannot exchange an authorization code for an access token, since the malicious application does not have the code verifier.
Intercepting the code challenge
is also useless because SHA256 is a one-way hashing algorithm and cannot be decrypted.
The following diagram represents the PKCE protocol flow:
PKCE Benefits
PKCE offers many security benefits that have made it a widely adopted standard among developers that implement OAuth 2.0. These benefits include:
Eradication of code interception attacks:
Without PKCE, an attacker who intercepts the authorization code can potentially exchange it for an access token. PKCE prevents this attack method by requiring acode verifier
with every exchange, ensuring that only the original client that started the flow can obtain the token.Backward compatibility:
PKCE can be used with any IdP that supports it, while still being compatible with servers that do not. This is because PKCE is simply an extension of OAuth.Standardization and wide adoption:
PKCE has seen wide adoption, especially on mobile and SPA's, due to its tremendous security benefits.Dynamic secrets:
PKCE uses a dynamic secret that’s generated for each authorization request, which reduces the risk associated with a compromised client secret.
PKCE Best Practices
If you are implementing a PKCE protocol flow, you should adhere to the following best practices to ensure that your application is properly secured:
Generate unique code verifiers:
Each authorization request should contain a unique and dynamically generatedcode verifier
. Always generating a newcode verifier
helps ensure that thecode challenge
is not predictable and helps prevent replay attacks.OpenZiti BrowZer always generates a dynamic/unique
code verifier
for you automatically.Use high-entropy code verifiers:
Acode verifier
should be generated using a cryptographically secure random generator, making it impossible to guess. It should have a minimum length of 43 characters and a maximum length of 128 characters.OpenZiti BrowZer uses a cryptographically strong random number generator to generate 32 random bytes and then encodes them to produce a 110-character
code verifier
for you automatically.Use SHA-256 hashing:
Theplaincode challenge method
should only be used if the client is unable to support theS256
method.S256
is a one-way hash, which further ensures that only the client that has thecode verifier
can exchange an authorization code for an access token.OpenZiti BrowZer implements
S256
code challenges for you automatically.Implement time limits: A
code verifier
and the transformedcode challenge
should have a short lifespan, which prevents them from being reused repeatedly.OpenZiti BrowZer implements unique, per-auth-attempt, ephemeral auth flows, and no security-related data is stored outside of session storage. The data only exists in session storage during the PKCE protocol flow (typically less than one second).
PKCE Implementation Challenges
PKCE offers numerous benefits, but it still presents many challenges you should note if you attempt to implement it. These challenges include:
Algorithm compatibility:
Different IDPs might support different hash algorithms for transforming acode verifier
into acode challenge
. This can make it difficult to ensure compatibility across different components.OpenZiti BrowZer manages this automatically (and is compatible with many IDPs).
Complexity:
To be done correctly, PKCE certainly introduces additional complexity to the authorization code flow implementation. You must managecode verifier
andcode challenge
data correctly as well as handle their transformations and comparisons securely.OpenZiti BrowZer manages this for you automatically.
Code verifier storage:
PKCE requires the client to store the originalcode verifier
until the authorization code is exchanged later in the protocol flow. Ensuring secure storage and retrieval of thecode verifier
can be challenging, especially for public clients (which all web browsers are).OpenZiti BrowZer manages this for you automatically (in short-lived session storage).
Security misconfiguration:
Implementing PKCE incorrectly or failing to properly validate yourcode challenge
will introduce the risk of security vulnerabilities. For instance, if thecode challenge
is not validated, attackers can forge counterfeit challenges and bypass security measures.OpenZiti BrowZer manages the
code challenge
data for you automatically.Risk of overconfidence:
While PKCE provides a strong security layer, it doesn’t eliminate the need for other security measures, such as secure communication (HTTPS), and proper access control.Relying solely on PKCE alone can lead to security gaps.
In addition to everything discussed above, OpenZiti BrowZer allows you to make your web application completely invisible to threat actors while still giving your authorized users easy access.
Authorized users who succeed with PKCE-based access to the overlay network that hosts your web app will also enjoy end-to-end encryption (xchacha20poly1305) of all data that transits between the browser and the web server, even before the data hits the wire. Once on the wire, the data will also have two more layers of encryption.
The Web Cloak of Invisibility
The tie-in between OpenZiti BrowZer and PKCE is how this kind of authorization flow is used to enable your authorized users (who exist out on the internet) to simply use a browser, and a couple of clicks, to gain visibility, and access, to a web application that is otherwise completely invisible to everyone else (notably, malicious actors).
You may recall that BrowZer's zero-trust capabilities enforce an "authenticate before connect" flow. Users of your web app must first perform a successful single sign-on (SSO) that provides a strong assertion of the user's identity.
The Identity Provider (IDP) you associate with your network is up to you. It could be a cloud-based instance of AzureAD, Okta, or Auth0 (and these IDPs could even federate out to Google, GitHub, or dozens of other providers)...
...Alternatively, you can also self-host an open-source IDP instance of Keycloak, Authentik, ...whatever. If the IDP is OIDC-compliant, browZer will work with it.
BrowZer will automatically use all the PKCE mechanisms discussed earlier in this article when interacting with your IDP. You don't need to modify your web app in any way.
When the PKCE flow completes, the IDP delivers the access token, and BrowZer stores it locally in the browser's session storage.
e.g.:
This access token is subsequently used to authenticate the user onto the Ziti Overlay network where your web app resides.
Note that although the user may have successfully authenticated with the IDP, the user represented by the IDP access token must still be known by the Ziti network.
If the user is not known by the Ziti network, the network authentication attempt will be rejected, and no access to the protected web app will occur.
"Authenticate before connect" literally means that a successful network authentication must occur before any web app connect attempts can succeed. If the Ziti network doesn't know who you are, the web app is invisible to you.
Secure handling of the Access Token
For security reasons, the IDP access token is never persisted by BrowZer. The access token never leaves the browser's memory and is thus ephemeral.
The instant the browser Tab is closed, all session storage is purged by the browser, and the access token is gone forever. This makes the access token more secure and unavailable to bad actors who might somehow gain access to the user's laptop or mobile phone.
Access tokens also have an expiration time that IDP admins can configure to their preference (an hour, a day, etc). So even in the rare case where a user left a browser Tab open, and then somehow lost their device, the access token would eventually expire, BrowZer would detect this, and Ziti network access would immediately end.
Wrap up
Do you host a web app and want to be invisible to malicious intruders?
Do you want your users to have easy access from anywhere with no additional software on their client devices?
Do you want to do all this without making any modifications to your web app?
If you are nodding yes, then we hope you'll start a conversation with us about BrowZer.
Subscribe to my newsletter
Read articles from Curt Tudor directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by