Philippe De Ryck

Hi, I'm Philippe, and I help developers protect companies through better web security. As the founder of Pragmatic Web Security, I travel the world to teach practitioners the ins and outs of building secure software.

Want to learn more about OAuth 2.0 and OpenID Connect?

Save yourself days of digging through dozens of specs with this online course

More information

From the Implicit flow to PKCE: A look at OAuth 2.0 in SPAs

About a year ago, the OAuth 2.0 Implicit flow became deprecated. That decision caused a lot of confusion and frustration. In this article, we analyze the different OAuth 2.0 flows to find out why the OAuth working group made that decision. Read on to find out about current best practices for using OAuth 2.0 in modern web applications.

7 May 2020 OAuth 2.0 & OpenID Connect OAuth 2.0, PKCE, Single Page Applications

Summary

The Implicit flow is deprecated for web applications because the Authorization Code flow with PKCE is cleaner to implement. Note that at the time of this writing, no new attacks have been discovered against the Implicit flow. It’s just a relic from a different web, which we no longer need today. New web applications being built today should definitely use the Authorization Code flow with PKCE. Existing projects that are using the Implicit flow should be upgraded when feasible.

Good. Now that you know that the Implicit flow is deprecated, let’s take a look at why the Authorization Code flow with PKCE is considered better.

The original Authorization Code flow

The core OAuth 2.0 specification defines three flows that involve a user, of which one is the Authorization Code flow. This flow defines how the user can authorize a backend web application to access resources on their behalf. The diagram below illustrates the original Authorization Code flow.

The Authorization Code flow where the client is a backend web application
The Authorization Code flow where the client is a backend web application

The client initializes the flow by redirecting the user’s browser to the security token service (Step 2 and 3) When the user authenticates and authorizes the client to act on their behalf (Step 4 to 7), the security token service sends control back to the client with another redirect (Step 8 and 9). This second redirect contains an authorization code, which the client can exchange for an access token and optionally a refresh token (Step 10 and 11).

During the code exchange (Step 10), the client is required to authenticate with its client credentials, also known as the client ID and client secret. Note that modern implementations also support alternative client authentication mechanisms, including key-based authentication with mTLS or JWT tokens. This client authentication is crucial to ensure that the actual client is exchanging the authorization code. Without this client authentication, an attacker could steal an authorization code from the network or the user’s browser and exchange it for tokens.

Of the three original OAuth 2.0 flows involving the user, the Authorization Code flow is the most secure. The flow only transfers the authorization code through the user’s browser. This authorization code can only be used once, has a limited lifetime, and requires client authentication before it can be exchanged. In this flow, access tokens and refresh tokens never pass through the browser. They are only passed over the backchannel (Step 11).

The security of the Authorization Code flow relies on the fact that the client runs in a secure server-side environment. Such clients have access to secure storage areas, making it possible for them to handle the client credentials securely. Additionally, the Authorization Code allows the client to obtain a refresh token, giving it long-term access to a resource on behalf of the user. The refresh token also needs to be stored securely and requires client authentication when used.

The Implicit flow

The original OAuth 2.0 specification also defines the Implicit flow, where the client is a frontend web application. At that time, frontend applications were more traditional AJAX applications and not the advanced Single Page Applications we have today. Regardless of the type of frontend application, the Implicit flow is built to address certain limitations of such clients:

  • Frontend applications cannot protect a client secret, so there is no client authentication the Implicit flow
  • Frontend applications cannot easily make cross-origin requests (at that time), so the Implicit flow puts an access token in the redirect URI
  • Frontend applications cannot store tokens securely, so the Implicit flow does not allow issuing refresh tokens

The Implicit flow sounds entirely different than the Authorization Code flow, even though it uses the same redirect mechanism to request authorization from the user. The diagram below shows the different steps in the Implicit flow.

The Implicit flow with a frontend web application as a client
The Implicit flow with a frontend web application as a client

Similar to the Authorization Code flow, the client initializes the flow by navigating the user’s browser to the security token service (Step 1 and 2). After authenticating the user and requesting the necessary authorization, the security token service issues the access token to the client. It embeds the access token in the fragment of the URL and uses that URL to redirect the flow back to the client application (Step 7 and 8). The client can read the URL fragment and extracts the access token, allowing it to make API calls on behalf of the user.

There is typically one more step, which is not visible on this diagram. When the client has extracted the token from the URL, the client will rewrite the URL to remove the token. That counters accidental leakage or social engineering attacks where the user is tricked into copy/pasting of the URL.

The security of the Implicit flow relies on two fundamental properties. First, a client has to register its redirect URIs for steps 7 and 8 up front. This requirement prevents an attacker from impersonating a client since the token will always be sent to the client’s legitimate redirect URI. Which brings us to the second crucial property: the attacker should not be able to extract the access token from the client. If the client suffers from an XSS vulnerability that allows the attacker to extract the token, the attacker will be able to abuse that token.

Learn all about current best practices for OAuth 2.0 and OIDC

This online course saves you countless hours of digging through specs

More information

The Authorization Code flow in mobile applications

When the original OAuth 2.0 specification was released, mobile or native applications were not in scope. It did not take long for mobile applications to become a representative client type for OAuth 2.0. Similar to frontend web applications, mobile applications cannot protect a client secret. As a consequence, mobile applications cannot use the Authorization Code flow intended for backend applications. They would be able to use the Implicit Flow, but that flow does not issue refresh tokens, an important feature for mobile applications.

The (insecure!) Authorization Code flow without client authentication
The (insecure!) Authorization Code flow without client authentication

So what if mobile applications use a modified version of the Authorization Code flow, where the client authentication in step 10 is dropped, as shown above? In such a modified flow, the only exposed piece of information is the authorization code transferred in steps 8 and 9. The code is only valid for once and is shortlived, so this seems to be acceptable. Unfortunately, on mobile, that risk is not acceptable, as a malicious application can easily intercept the redirect between the system browser and the application in step 9. When the attacker tricks the user into installing such a malicious application, that application could start intercepting authorization codes being sent to the legitimate client and exchange them for an access token and refresh token.

Such an attack is hazardous. That’s precisely why the OAuth 2.0 working group designed a modified Authorization Code flow that does not suffer from this weakness. The additional security comes from a mechanism known as PKCE, or Proof Key for Code Exchange in full. PKCE is pronounced as “pixie” and is defined in RFC 8252. The diagram below shows the modified Authorization Code flow with PKCE enabled. The PKCE-specific parts are marked in green with italic text.

The Authorization Code flow with PKCE enabled
The Authorization Code flow with PKCE enabled

The first change brought on by PKCE happens during the initialization of the flow. The client generates a random string with a length between 43 and 128 characters, known as the code verifier. Next, the client calculates the SHA256 hash of that string, which we call the code challenge. The client stores the code verifier and includes the code challenge in the first request. The code snippet below shows an example of a code verifier and its corresponding code challenge.

Example of a code verifier and code challenge


Code verifier (random string):
oVWzd3h4gUSBGlTLrIQwvDK7YfX3IfdI_06v2u9HM212kgM4mFGGqsv7Cc6kYFGkJkbaPggLcGbsxuavP6whzjM1HFxQMsy6ZvMNfq

Code challenge (base64-urlencoded SHA256 hash):
k8uV_isUCf9QixOgvjEYrJCATOCsoEdG7GSFTT4ma4Y

Through the redirect mechanism, the security token service receives the client’s code challenge. The security token service stores this challenge, along with the authorization code that is being issued (Step 4). The flow returns to the client, just like a normal Authorization Code flow.

When the client exchanges the authorization code for tokens, it makes a direct API call to the token endpoint of the security token service (Step 11). This is the request that requires client authentication for confidential clients. Public clients cannot authenticate here, but With PKCE enabled, they can include the original code verifier in the request. When the security token service receives this code verifier, it can also calculate the SHA256 hash of that verifier (Step 12). Now, the security token service can compare that hash to the code challenge it received from the client in step 3.

PKCE is an elegant way to ensure that only the legitimate client can exchange an authorization code for the corresponding tokens. Let’s take a closer look at the security guarantees we get from PKCE.

  • The use of a hashing function, such as SHA256, ensures that the code challenge is uniquely linked to the code verifier
  • Due to the properties of a hashing function, there is no feasible way to derive the input from the hash. In other words, the attacker cannot derive the code verifier from a code challenge alone.
  • The code verifier is stored by the legitimate client, so a malicious application is unlikely to have direct access to that stored information

With these properties, PKCE effectively links the initialization of the flow to the finalization of the flow. PKCE ensures that only the client instance that started the flow in step 2 will be able to finish it in step 11. A malicious application intercepting an authorization code will fail to provide a valid code verifier. As a result, the security token service will not issue tokens to the malicious client.

In a nutshell, you can think of PKCE as a one-time password to authenticate a specific instance of a public client.

Securing applications with OAuth 2.0 and OIDC is hard!

Join this online training to learn all about security do's and don'ts in modern applications

More information

The Authorization Code flow in web applications

So what about web applications? The web surely has its flaws and problems, but modern best practices eradicate quite a few attack vectors. A well-configured client is unlikely to suffer from the problems that we observed on mobile platforms, where attackers can intercept the redirect URI. The use of strictly configured redirect URIs, HTTPS everywhere, and HTTP Strict Transport Security all help in preventing such attacks.

Because of these improvements, frontend web applications are a lot more secure than they used to be. In such a well-secured application, one can argue that even the Implicit flow is not all that bad. It is an efficient way to obtain an access token to use in a frontend application. This way of thinking is perfectly valid. There is nothing fundamentally broken about the Implicit flow.

However, from a high-level point-of-view, the Implicit flow lacks elegance. Access tokens are embedded directly in the URL. The URL requires rewriting to hide that sensitive information in case social engineering attacks trick the user into copy/pasting the URL. Refresh tokens are not available, so clients are stuck with a silent renew in a hidden iframe.

Additionally, modern browsers fully support cross-origin requests, which allows clients to make direct calls to the token endpoint of the security token service. Nothing prevents a frontend web application from running a PKCE-based Authorization Code flow.

Because of all these reasons, the OAuth 2.0 Security Best Current Practice considers the Implicit flow to be deprecated. It is not an urgent “OMG it’s broken” type of deprecation, but mostly a recommendation to upgrade to a better and cleaner mechanism. Running an Authorization Code flow with PKCE prevents disclosing tokens in the URI of the browser. This flow allows the use of refresh tokens, albeit with additional protections enabled.

To summarize, the following recommendations apply for web applications:

  • If the application is already using the Implicit flow, there is no need for an immediate change. The application should eventually be upgraded to use the PKCE-based Authorization Code flow.
  • New projects use the Authorization Code flow with PKCE. Their configuration with the security token service should prevent any other flow.


About Dr. Philippe De Ryck

Hi, I'm Philippe, and I help developers protect companies through better web security. Learn more about my security training program, advisory services, or check out my recorded conference talks.

Want to learn more about OAuth 2.0 and OpenID Connect?

Save yourself days of digging through dozens of specs with this online course

More information
Philippe De Ryck

Dr. Philippe De Ryck

Hi, I'm Philippe, and I help developers protect companies through better web security. As the founder of Pragmatic Web Security, I travel the world to teach practitioners the ins and outs of building secure software.


Talks and workshops

You will often find me speaking and teaching at public and private events around the world. My talks always encourage developers to step up and get security right.


Articles

Security is often about small nuances. In my articles, I dive deeper into various security topics, providing concrete guidelines and advice. My articles also answer questions I often get while speaking or teaching.


Security resources

Getting security right is all about knowledge. I strongly believe in sharing that knowledge to move forward as a community. Among my resources, you can find developer cheat sheets, recorded talks, and extensive slide decks.


Mailing list

Subscribe to the Pragmatic Web Security mailing list to stay up to date on the latest activities and resources.

Subscribe

\