A Primer on OAuth 2.0 for Client-Side Applications: Part 3
- Johann Nallathamby
- Director - Solutions Architecture - WSO2
Introduction
This is the third part of a four-part article series. In the first part, I looked at the broader categories of client-side applications (CSA), their legacy and more recent authentication and API authorization standards, as well as their pros and cons. In the second, I discussed the challenges in CSAs, standard and non-standard recommendations that are widely employed to overcome those challenges, and some of the non-standard solution patterns for single-page applications (SPA) to overcome limitations in the existing standards I discussed in part 1, specifically regarding OAuth 2.0 proxy patterns.
In this part, I will discuss the important security properties of cookies that make them a suitable candidate for a sender-constrained token technology and more non-standard solution patterns, specifically sender-constrained token patterns.
Tokens Love Cookies
Although the stateful proxy pattern and the stateless proxy pattern could be acceptable for most users, for some users, it may still not be acceptable from a user experience perspective due to it not following a clean SPA architecture.
For those users who do not find the proxy patterns suitable, how do we secure access tokens and refresh tokens from being leaked? Let’s look at some solution patterns to satisfy those users.
In the standard OIDC grant flows we’ve seen so far, the access token and refresh token are returned as JSON attributes in the HTTP response body. The access token and refresh token need to be stored in the browser in order for the client to use them when invoking APIs or refreshing access tokens respectively. For this purpose, you would typically use the HTML5 web storage (localStorage or sessionStorage).
The main drawback of using the HTML web storage is that it is vulnerable to XSS, has a larger attack surface, and can impact all application users on a successful attack. This is considered one of the primary ways a token gets leaked.
HTTP cookies is an alternate client-side storage mechanism. They also provide additional security properties such as the “httpOnly” flag and “secure” flag. With the “httpOnly” flag, the browser makes sure that no client-side script can read these cookies. With the “secure” flag, the browser makes sure that the cookies are only transmitted to the server when the connection is HTTP over TLS.
Cookies also have a drawback. Cookies are vulnerable to cross-site request forgery (CSRF). However, there are effective CSRF prevention mechanisms, such as the synchronization token pattern or the double submit cookie pattern, which are supported by most modern web frameworks. Therefore, cookies are preferred over HTML5 web storage with enough CSRF protection ensured.
Even though they are more secure to store your access tokens, cookies can cause some developer headaches, depending on if your applications require cross-domain access to work. Using AJAX, your server-side can also notify browsers whether credentials (including cookies) should be sent with requests with CORS.
These security properties for cookies lead them to be considered as a suitable candidate for technology to implement sender-constrained token patterns. These patterns allow you to bind the tokens used in OAuth 2.0 to the OAuth 2.0 client, which was originally used to request them. Collectively they are known as “sender-constrained token” patterns.
You may find some API security implementations that rely only on the security properties of a secure HTTP-only cookie to store the access token. You should note that this is not an optimal solution. Firstly, it does not follow the standard Bearer Token Usage (RFC 6750) [1] profile. And secondly, it is vulnerable to CSRF.
To overcome both of these drawbacks, the following two sender-constrained token patterns will help.
Split Access Token Cookie Pattern
The inspiration behind this pattern is derived from the “double submit cookie” pattern used for CSRF protection. In this pattern, the access token is made up of two parts.
- One part is known as the public token. This part is sent to the OAuth 2.0 client in the usual OAuth 2.0 token response JSON attribute. The client can choose to store this part of the token either as a non-HTTP-only cookie or in the local storage of the browser. Storing this part in the HTML page will make it harder to pass this token around when navigating between different pages.
- The second part is known as the secret token. This part is stored as a secure HTTP-only cookie. The cookies must be stored against the domain of the API Server. Therefore, in order to utilize this pattern, there are two possibilities:
- The OAuth 2.0 authorization server and the API Server have to be running under the same root domain. If so, the secret part of the access token can be pre-generated during the authorization request phase and stored in the browser.
- The OAuth 2.0 authorization server’s APIs have to be proxied via the API Server.
When the OAuth 2.0 client needs to make an API invocation, it will invoke the API Server with the part of the token that is stored in the JavaScript accessible storage, as the Bearer token. However, along with this invocation, the browser will also send the secret part of the token which is stored in the HTTP-only cookie, as it was stored against the same domain. Once both these parts reach the API Server, the API Server will construct the full access token by combining both parts and introspecting it at the OAuth 2.0 Introspection endpoint.
The same technique is utilized for the refresh token as well.
An example of this implementation can be found in WSO2 API Manager 3.0.0 and above. It is used to protect the access tokens that are issued to the API Developer Portal and the API Publisher, which are SPAs.
Binding Token Cookie Pattern
This pattern follows the same philosophy as the “Split Access Token Cookie” pattern. However, instead of splitting the access token into two parts, it introduces an additional binding token as the second part. This binding token corresponds to the secret in the earlier pattern we saw. The OAuth 2.0 introspection endpoint implementation will have to be extended to support the validation of the association between the access, refresh, and binding tokens.
An example of this implementation can be found in WSO2 Identity Server 5.10.0 and above. It is used to protect the access tokens that are issued to the User Portal. which follows a pure SPA architecture.
However, there is a downside to this pattern; it makes the API security layer stateful. What does that mean? We will look into that in the next section.
JSON Web Tokens (JWT) as OAuth 2.0 Bearer Access Tokens
Please refer to the post on “JSON Web Tokens (JWT) as OAuth 2.0 Bearer Access Tokens” [2].
Revisiting “Split Access Token Cookie” and “Binding Token Cookie” Patterns
It should be clear by now, that if you are using the “split access token cookie” pattern, the ideal way to split the access token in two would be to split right after the second period, so that the JWT header and JWT payload are stored in the JavaScript accessible storage to be used in the SPA, and the signature (secret) is stored as a secure, HTTP-only cookie.
And also you should be able to understand now, why I said that the “Binding Token Cookie” pattern is not a stateless API security architecture pattern. I said that because, for each API invocation, at the API Server, the bearer access token has to be validated with its associated binding token.
Summary
In this part of the series, I discussed the important security properties of cookies that make them a suitable candidate for a sender-constrained token technology, and more non-standard solution patterns, specifically sender-constrained token patterns.
In the next part, I will be discussing the latest and upcoming standards following the sender-constrained token pattern aimed at solving the challenges in CSAs, and solution patterns being employed to improve the CSA user experience.
References
[1] https://tools.ietf.org/html/rfc6750
[3] https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage