Backend for Frontend

Improve Single Page Application Token Security with Lightweight OAuth Proxy

Throughout the years, creators of SPA applications have used different approaches to securely storing OAuth tokens. Recent changes introduced by browsers and a better understanding of new forms of attacks and risks led to new recommendations and patterns for storing tokens securely by, for example, single page applications (SPAs). This article describes a pattern based on a thin Backend For Frontend (BFF) component, OAuth Proxy, that can be added to a Single Page App to improve the security of tokens.

A Little Bit of Background

When the original OAuth specification was introduced, the OAuth Implicit Flow was the main mechanism to get tokens by single page applications. Now, the Implicit Grant type is considered insecure mainly because it does not include any type of client application authentication when the application obtains a security token. With the Implicit Flow, the authorization server has no possibility to check the authenticity of the client app. How we can mitigate this issue?

Newer OAuth recommendations propose using Authorization Code Grant Type with Proof Key of Code Exchange (PKCE) coupled with, for example, client authentication disabled (set to none).

Let’s assume that your application already has the token and it got the token in a secure way. What then? How should the token be stored by your single page application so that the user does not need, for example, to relogin every couple of minutes? Is it even safe to store the token inside a browser?

Browser Token Security

Unfortunately, all current solutions for storing tokens in browsers have significant downsides. You can store tokens in browsers cookies, however, they are vulnerable to Cross Site Scripting (XSS) as well as Cross Site Request Forgery (CSRF). Alternatively, tokens can be stored in the browser’s local storage, this though is also vulnerable to XSS.

The third, not so commonly used pattern is to store the token in JavaScript memory, with the best implementations using Web Workers to store the tokens in a separate scope to the rest of the application. This however has a significant downside - it does not provide persistence of the token between page refreshes and forces token fetch per every browser tab. The aforementioned recommendations document mentions the risks of storing access tokens in the browser.

Considering the problems above, a question worth asking is - do we need to store the tokens in the browser? What alternatives are there?

Alternative Approach - Backend For Frontend (BFF) Components

If we want to achieve a better security level for a single page application, there is an alternative. The proposed solution introduces a Backend For Frontend component, OAuth Proxy, responsible for getting tokens from authorization servers, storing them, and sending them to the resource server APIs. The solution is versatile enough that it can be used even by multiple SPAs if needed.

See the diagram below:

[mermaid-begin]
sequenceDiagram participant spa as Client SPA participant proxy as OAuth Proxy participant as as Authorization Server participant rs as Resource Server spa ->> proxy: Login proxy ->> as: Get token as -->> proxy: Token proxy -->> spa: Same-site cookie spa ->> proxy: Call API proxy -->> proxy: Add token to the SPA's original request proxy -->> rs: Call API rs -->> proxy: Response proxy -->> spa: Forward the response

In this diagram, we’re introducing a new lightweight backend component - OAuth Proxy - responsible for three actions: getting tokens from an authorization server, transforming cookies into tokens, and reverse proxying requests with tokens to APIs of a resource server.

  1. A single page application’s client application makes a login request to the BFF component - OAuth proxy.

  2. The OAuth Proxy client application gets a token from an authorization server.

    In reality, the proxy could use any of the OAuth grant types to authorize the client and any of the OAuth client authentication methods to authenticate the client. Preferably, the Authorization Code Flow is used to keep the token in the context of the user and get it on their behalf. Although it requires the user to provide their consent, in this case, it is more secure than, for example, using the client credentials flow where the client would be able to get data about multiple users instead of one.

  3. The authorization server mints and issues a token to the OAuth proxy client application.

  4. The OAuth proxy client application encrypts the token and puts it into a cookie. Then, it sends the cookie to the browser.

    For more information about the cookie, see the How is Storing Cookies Made Secure section.

    Alternative

    Alternatively, the proxy could store the token inside a database making it a stateful application. It is, however, a more complex solution in a case there are multiple SPAs using the same proxy.

  5. The single page application calls the resource server APIs through the OAuth client application.

    The request contains the cookie the SPA got in the 4th step.

  6. The proxy extracts the token from the cookie, decrypts it, and proxies the SPA’s API call to the resource server with the access token added to the request.

  7. The resource server responds to the proxy with the requested resources.

  8. The proxy forwards the response to the single page application.

How is Storing Cookies Made Secure

Cookies are vulnerable to attacks, right? Well, the cookie returned to the SPA needs to have a few properties added to make this secure:

  • Cookie has to be HttpOnly

    It guarantees that the cookie cannot be read from JavaScript. Even if an XSS attack is successful, the cookie cannot not be accessed by the attacker.

  • Cookie has to be Secure

    It guarantees that the cookie will only be transmitted to HTTPS APIs.

  • Cookie has to have SameSite set to Strict

    It guarantees that the cookie will only be sent if the domain of the request matches the site that the user visited, thus mitigating CSRF.

The OAuth Proxy has to have some mechanism for matching the cookie to the token which is supposed to be sent to the API.

There are two main ways to approach it:

  • Keeping a map of cookie -> token relations

    In this case, the cookie can be an opaque string.

  • Encrypting the token and returning it as a cookie

    In this case, the OAuth Proxy encrypts the cookie and sends it to the SPA. When SPA makes a call to an API, Proxy decrypts the cookie and sends the token to the Resource Server.

Is Your Application Still a SPA

Well, that’s a good question. One may argue that adding a backend component to a single page application no longer makes it a SPA, but:

  • Changes to the application are minimal and the OAuth proxy itself is a lightweight component.

  • You don’t have to change the deployment model of your application.

    You can still deploy your SPA using CDN if that’s what you were doing (and if not, you probably should).

    The Proxy component has to be deployed separately, but you can use tools like AWS Lambda to reduce the management cost.

  • Adding such a proxy improves the security of your users' data.

Calling your application a single page app is just a matter of nomenclature. If your data and the data of your users can be made more secure with low costs and minimal effort, you should strive for it.

Summary

Adding a Backend For Frontend OAuth Proxy component to Single Page Applications has significant advantages and removes most of the risks related to OAuth security tokens. If a need to deploy and manage an additional component does not bother you, consider adding the proxy to your SPA to make the data more secure.

Sign up for a free Cloudentity Authorization SaaS account.

You can integrate your Single Page Application or your OAuth Proxy backend component with the Cloudentity platform to use its powerful authorization and identity management features!

You can, for example, easily configure the OAuth Proxy client application to get authorized with the Cloudentity platform built-in authorization server using, for example, the OAuth Authorization Code with PKCE flow mentioned above.

Updated: Aug 31, 2022