A Primer on OAuth 2.0 for Client-Side Applications: Part 1

  • By Johann Nallathamby
  • 22 Apr, 2020

Introduction

Client-side applications (CSA) are becoming an increasingly popular technology to build applications owing to the advanced user experience that they provide consumers. Authentication and API authorization for CSAs are also becoming equally popular topics that many developers have a hard time getting their heads around. In this four-part article series, I will attempt to demystify some complexities and misconceptions surrounding this topic and help you better understand the most important features to consider when choosing an authentication and API authorization solution for CSAs. I will begin the series by discussing the broader categories of CSAs, their legacy and more recent authentication and API authorization standards, and their pros and cons.

There are two main kinds of CSA technologies that are very commonly used across all verticals:

  1. Single Page Applications (SPA)
  2. Mobile Native Applications

While CSAs have existed before the introduction of OpenID Connect (OIDC) and OAuth 2.0, the advent of OAuth 2.0 and OIDC has definitely stirred up a debate on the right way of performing authentication and API authorization for CSAs. While OIDC has become the de facto standard to authenticate users in CSAs, OAuth 2.0 has become the defacto standard to authorize API invocations in CSAs.

CSAs are known as “public clients” in OAuth 2.0 specifications. There are two reasons for this:

  1. They cannot store the client’s password completely securely on the client-side
  2. They cannot store the access tokens completely securely on the client-side

In SPAs, the client password and access tokens have to be stored in the user’s browser; in mobile native applications, they have to be stored in memory that is accessible by the application. Since both these kinds of applications store the client password and access tokens outside the control perimeter of the application, we cannot consider the client password or access token as entirely confidential. There are two broad categories of implementations for authentication and API authorization we see in CSAs:

  1. Back-channel authentication and API authorization
  2. Front-channel authentication and API authorization

Back-channel Authentication and API Authorization

The following implementations are the most common back-channel flows you may come across:

  1. Legacy username/password authentication and token-based API authorization
  2. OIDC resource owner password grant flow

Legacy Username/Password Authentication and Token-Based API Authorization

Figure 1: Legacy username/password authentication and token-based API authorization

The CSA will receive either the user info and API token, or the API error in the HTTP body of the authentication response.

OIDC Authentication Using Resource Owner Password Credentials Grant Flow

Post the advent of OIDC, the above legacy flow evolved into the following:

Figure 2: OIDC authentication using Resource Owner Password Credentials grant flow for CSA

OAuth2 authorization servers will not issue client passwords or other client credentials to CSAs for the purpose of client authentication. They may, however, issue a client password or other credentials for a specific installation of a CSA on a specific device. If the CSA is issued a client credential, it must use it to authenticate to the OAuth 2.0 token endpoint.

Pros and Cons of Back-channel Flows

There are pros and cons of the above back-channel flows. The main argument for back-channel flows is that they do not involve any redirections and therefore provides the best possible user experience. However, the arguments against back-channel flows are:

  • They don’t provide a single sign-on experience to the users
  • Users are forced to provide their username/password pair to the CSA which opens up possibilities for:
    • Password phishing attacks on 3rd party untrusted applications
    • The CSA getting compromised and in turn, the password getting compromised

Front-channel Authentication and API Authorization

If the CSA is built to support only username/password authentication, then the above back-channel flows may work just fine for most users, especially if we are talking about first-party applications. But if the CSA needs to support identity federation in order to enable multi-factor authentication (MFA), social login, etc., then back-channel flows between the CSA and the identity provider may not work, and you may have to use front-channel redirect-based flows between the CSA and the IAM.

The following implementations are the most common front-channel flows you may come across:

  • Legacy identity federation and API authorization
  • OIDC implicit grant flow
  • OIDC authorization code grant flow
  • OIDC authorization code grant flow with Proof Key for Code Exchange (PKCE)

Legacy Identity Federation and API Authorization

Figure 3: Legacy identity federation and API authorization for JavaScript applications

The legacy flow was typically implemented in JavaScript applications. The user’s session was typically tracked via an HTTP session cookie. The API authorization was also based on the same cookie. The session data for the logged-in user would be returned to the browser in the authentication response DOM to be read on boot when loading the SPA from the HTTP Server. Alternatively, a dedicated backend API for retrieving the session data may be used.

OIDC Authentication Using Implicit Grant Flow

The primary reason for the creation of the implicit flow was due to an old limitation in browsers. The implicit flow in OAuth 2.0 was created in 2010 when JavaScript in browsers could only make requests to the same domain that the page was loaded from. However, the standard OAuth 2.0 Authorization Code flow requires that a POST request is made to the OAuth 2.0 server’s token endpoint, which is often on a different domain than the app. That meant there was previously no way to use this flow from JavaScript. The Implicit flow worked around this limitation by avoiding that POST request and returning the access token immediately in the redirect.

Post the advent of OIDC, the legacy flow in Figure 3 evolved into the following:

Figure 4: OIDC authentication using implicit flow for SPA (showing happy path scenario only for brevity)

Since SPAs are considered OAuth 2.0 public clients, and they cannot legitimately authenticate the IAM OAuth 2.0 token endpoint using client credentials, we return the id_token and the access token in the response to the OAuth 2.0 authorization call. For this reason, it is mandated that the redirect URI of an OAuth 2.0 public client must be registered with the OAuth 2.0 authorization server beforehand and verified against the redirect URI in the authorization request, which will be used to deliver the access token and ID token.

The key benefit of using the implicit grant flow is that it improves the responsiveness and efficiency of the client since it reduces the number of round trips required to obtain an access token. One of the important features of the implicit grant flow is that it returns the access token as a fragment URI, which prevents the access token from being sent to the client’s backend server component and getting logged in an HTTP log file, which is a common way that sensitive information gets leaked.

However, the access token returned in the authorization response as a redirect opens up a number of security concerns:

  • The access token is visible as part of the URL address bar to end users at the user agent. This increases the risk of attackers stealing the access token.
  • The client’s redirect_uri gets stored in the browser’s history which may inadvertently expose the access token. Many people even sync their browser history which further increases the attack surface.
  • Unverified JavaScript running with browser extensions could read the access token if necessary permissions to read the URL has been given.
  • The redirect URL including the access token may get logged on the redirect step, in an intermediary proxy server’s HTTP logs.
  • The access token fragment may get disclosed through referrer headers.
  • The redirect URL is susceptible to XSS.
  • The redirect URL is susceptible to token interception attacks.

Modern browsers provide the ability for JavaScript to modify the address bar history using the Session History Management API, without the need to reload the browser. This improvement has made it possible for SPAs to remove the access token from the address bar history once it is received.

OAuth 2.0 public clients that utilize the implicit grant flow are susceptible to the token interception attack. In this attack, the malicious client can intercept the access token returned from the authorization endpoint within a communication path not protected by Transport Layer Security (TLS), such as inter-application communication within the client's operating system. In order to execute this attack, the malicious client must be able to register itself as a handler for the custom URI scheme in addition to the legitimate OAuth 2.0 client.

The implicit grant flow has always been a compromise compared to the authorization code flow due to its inherent security vulnerabilities:

  • The implicit flow doesn’t return a refresh token that allows you to refresh the access token without requiring user interaction, as it was seen as too insecure to allow that.
  • It is recommended to use short lifetimes and limited scope for access tokens issued via the implicit flow.

OIDC Authorization Code Flow

Implementing the implicit flow securely has always been challenging. In order to be extremely sure that your application is not vulnerable to the common threats to implicit grant flow, you have to thoroughly audit your source code, knowing exactly which third-party libraries you’re using in your application, have a strong Content Security Policy (CSP), and most importantly be confident in your ability to build a secure the CSA.

Today, Cross-Origin Resource Sharing (CORS) is universally adopted by browsers, removing the need for compromise. CORS provides a way for JavaScript to make requests to servers on a different domain as long as the destination allows it. This opens up the possibility of using the Authorization Code flow in JavaScript.

Though implicit grant flow is a technically feasible option for CSAs, due to its security concerns, more recently the authorization code flow is typically recommended for use with CSAs.

Figure 5: OIDC authentication using authorization code grant flow for CSA (showing happy path scenario only for brevity)

As with the implicit grant flow, since SPAs are considered OAuth 2.0 public clients, we do not expect the client to authenticate to the IAM at the OAuth 2.0 token endpoint. However, the redirect URI of the OAuth 2.0 public client must be registered and verified.

The benefits of using the authorization code grant flow are:

  • Short-lived and one-time use authorization codes reduce the attack surface.
  • The access tokens don’t get stored in the browser’s history as is the case with the implicit flow.
  • Since this flow issues a refresh token, the access token issued via this grant can be refreshed without requiring any user interaction.

As we all know, in security nothing is 100% secure. Everything is about mitigating risks. You cannot solve all the security problems, but you can eliminate the easy attacks. Some of the easiest attacks that have been identified are in redirects than post requests from browsers. As such, it is on that basis that the authorization code grant flow is considered a better choice for CSAs than the implicit grant flow. The downsides of the authorization code grant flow, however, are:

  • It is less efficient than the implicit grant flow since it has to make two roundtrips between the CSA and the IAM to get an access token.
  • The access tokens are still visible to end users, not as part of the URL address bar, but by inspecting the client-side storage.
  • The access tokens are still susceptible to XSS attacks as part of the web storage.
  • The authorization code is susceptible to the code interception attack.
  • If the refresh token is compromised, it can be used without client authentication.

Pros and Cons of Front-channel Flows

Front-channel flows also have their pros and cons. The arguments for these flows are:

  • They provide a single sign-on experience to the users.
  • Users provide their credentials only to the IAM system which mitigates possibilities of password leakage to a great extent.

The main argument against these flows is that they involve redirections and therefore do not provide the best possible user experience.

OIDC Authorization Code Flow with PKCE

Similar to how OAuth 2.0 public clients utilizing implicit grant flow are susceptible to access token interception attack, the OAuth 2.0 public clients that utilize the authorization code grant flow are susceptible to an authorization code interception attack. In this attack, the malicious client will gain access to the authorization code and use it to obtain the access token. The authorization code flow with PKCE [4] adds an additional step which allows us to protect the authorization code so that even if it is stolen during the redirect it will be useless by itself. In essence, PKCE uses a cryptographically random string that is used to correlate the authorization request to the token request, to make sure that the initiator of the OAuth 2.0 authorization request is the same as the initiator of the OAuth 2.0 token request.

Unfortunately, there is no such thing as perfect security. Especially in client-side applications, there are always many ways an application may be attacked. The best we can do is to protect them against common attacks and reduce the overall attack surface of an application.

Specifically, the authorization code flow with PKCE does completely protect an application from the attack where an authorization code is stolen in transit back to the application. However, once the CSA has obtained an access token, it will still have to store it somewhere in order to use it. Regardless of which flow it used to obtain the access token, it is still vulnerable to the same threats that are present when the access token is at rest at the client-side. You will still need to ensure you have a good content security policy and are aware of any third-party libraries you are using in your application.

Summary

In this part of the series, I’ve discussed the broader categories of client-side applications, their legacy and more recent authentication and API authorization standards, and their pros and cons. In following the article, I will discuss the challenges and recommendations for client-side applications and more recent standard and non-standard solution patterns that are employed to overcome some limitations in the standards discussed in this article.

Read the third and fourth articles of this series to learn more.

References

[1] https://readybytes.in/blog/secure-yet-simple-authentication-system-for-mobile-applications

[2] https://tools.ietf.org/html/rfc6749

[3] https://tools.ietf.org/html/rfc8252

[4] https://tools.ietf.org/html/rfc7636

About Author

  • Johann Nallathamby
  • Associate Director/Solutions Architect
  • WSO2