Potential risks of using OAuth2 in Single Page Applications

Potential risks of using OAuth2 in Single Page Applications

Learn what are some of security risks when we use OAuth2 for authentication and authorization in Single Page Applications

In the 1st part of the series, we developed a Single Page Application using Angular framework. SPAs are common pattern for building web applications now a days. But, there are some unique challenges when using OAuth with SPAs. I will try my best to explain some of them. The reason of the following challenges is that the code of single page application is kept inside browser. So, all the data which the application needs also lives inside the browser.

Issue with credentials

One simple limitation of JavaScript applications is that there is no way to have any sort of credentials in the app. In OAuth terms, these apps are considered Public Clients. If we put any kind of secret or API key in the source code, the user can easily click on View Page Source and can access the secret. There is no way to hide it from the user.

To solve this issue we need some mechanism for OAuth flow without needing client secret. That's exactly what PKCE extension provides. Some Identity Providers do not even generate client secret for single page applications. When we built the Angular app, we used angular-oauth2-oidc package to handle OAuth login flow, which uses PKCE extension by default.

Cross Site Scripting Attack

Cross-site scripting attack or XSS is one of the most dangerous attack. In this attack, attacker is able to run code within our application which looks just like legitimate code. They are able to access any data the app is storing itself like some sensitive data or access token. They are able to make API calls with the access token even if they can't see it.

Having strong Content Security Policy is one of the best way to defend against XSS attack. Using CSP, we can tell browser which domains are allowed to load the JavaScript from in our app. Thus, attacker won't be able to inject JavaScript from any other domain. There are some limitations to this approach. For example, if we use any CSS frameworks, analytics or advertisements in our app, then we need to allow these domains to load JavaScript from (otherwise app won't work properly). But, what if the JavaScript code downloaded from these sources is already attacked. In that case, our app will also be compromised. But that doesn't mean we should not use any third-party libraries. We need to be mindful of potential security risks associated with them.

Browser Extensions

Users of our web app may have installed some browser extensions which may also access the page. So, even if we have secured the app using strong content security policy, the user can still load external JavaScript using browser extensions. So, we never know what code is running inside our web app.

Storing tokens in browser

We have seen that it is nearly impossible to hide client secret and access token from external JavaScript code in a web app. To handle the issue with client secret, we can use PKCE extension and can get access token from Authorization server. Now, we need a way to securely storing received access token in the browser. Access token can be stored at one of the 3 places: Local Storage, Session Storage and Cookies. But, if our app's code can access the token from these locations, then external JavaScript also can. So, we need some way to restrict 3rd party JavaScript from accessing the token. Some possible workarounds are the following:

  1. Don't store access token at all. Keep this in app memory. This makes it much harder for an attacker to extract access token. But then, the access token can't be shared among multiple tabs or page refreshes.
  2. Another option is to store token in Service Worker. The storage used by Service Worker is completely isolated from browser storage. So, external JS won't be able to extract token from it. But then, even our main app won't be able to access the token. We need to make Service Worker an OAuth Client itself. It is very complicated to build. We can only ask Service Worker what to do, but we can not access the information inside it. And if our app can ask Service Worker to make some API call using token, then external JavaScript also can.

Securing Web App using Backend

We have seen in the previous sections that it is very difficult to secure the access token in a Single Page Application. There is always the possibility of stealing access token from app by attacker. But, what if even the web app don't have access to the token. Then, attacker can't stole it, right? But, how is it possible, you may ask.

The idea is to move OAuth flow and Access token storage into a dynamic backend server. This will be possible only if we use SPA framework and serve it from a backend server (like SpringBoot). This is a common pattern and then use backend for OAuth flow and proxy all the API requests through that backend. The backend can send an HttpOnly session cookie to the browser and thus even that can't be stolen from JavaScript. I will cover how to build and use this dynamic backend for single page application in the next part of this series.

Summary

In this article, we looked at some of the security concerns while storing credentials and access token in browser for single page applications. We also discussed some of the possible solutions. In the next article we will build a backend server to serve our single page application.

Until then, stay safe and keep learning...