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.
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:
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.
A single page application’s client application makes a
loginrequest to the BFF component - OAuth proxy.
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.
The authorization server mints and issues a token to the OAuth proxy client application.
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.
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.
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.
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.
The resource server responds to the proxy with the requested resources.
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
Cookie has to be Secure
It guarantees that the cookie will only be transmitted to HTTPS APIs.
Cookie has to have SameSite set to
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.
Ingredients of Our Cookie
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 -> tokenrelations
In this case, the cookie can be an opaque string.
tokenand returning it as a
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.
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.