Securing Tokens In A Progressive Web App

Security is at the top of the agenda for every technological development, especially so in the open world of the World Wide Web and Internet generally. The core underlying challenge is that the web and Internet were not designed with a modern level of security in mind. This makes creating web experiences that are both highly usable and highly secure difficult.

OAuth And Distributed Identity Services

In recent years technologies such as federated identity, OAuth ans Single Sign On have all become very popular on the web. Instead of having one hundred different user names and passwords for one hundred different web portals, users can have one username and password provided by a trusted partner (e.g. Azure Active Directory) and use that to log in to all their web portals without having to disclose the password to those systems. This is accomplished using a system of secure “tokens” that authorise users to access different systems.

An Example

For illustrations, let’s say we are building an Angular 7 Progressive Web App (PWA) that is authorised using OAuth against an Azure Active Directory. The Angular App accesses a web API that is hosted on an App Service in Microsoft Azure. APIs frequently request an OAuth token to be provided by the client (the PWA here) with every request.

Do Not Use Web Storage

A simple plan would be to write some Typescript / Javascript your PWA that logs the user in, gets the OAuth token from Azure Active Directory and stores it in the convenient Web Local Storage or Session Storage of the browser. The PWA can then load the token from web storage when it is needed and provide it to the API. This solution is simple and frequently implemented, but it is also fundamentally insecure! A discussion of why is beyond the scope of this article, but suffice it to say that web storage is vulnerable to simple cross site scripting attacks.

Similarly, client-accessible cookies are similarly vulnerable to very simple attacks.

Use Secure HTTP Only Cookies

To avoid the problems outlined above needs a little bit more work. My preferred approach is to login to the auth provider (e.g. Azure AD) via the PWA and immediately use the token retrieved to call a “login” method on the API (passing it the token) without storing the token retrieved from Azure AD anywhere on the client-side. The API would then validate the token passed in (for example using the ADAL library) and issue it’s own authorisation cookie (for example using simple ASP.Net forms authentication!). The cookies issued MUST have the “secure” and “HTTP Only) flags set. This means that the client-side Javascript / Typescript cannot read the cookie. The browser is responsible for dispatching the cookie with every HTTPS request to the API. This solves the cross site scripting problem.

CSRF / XSRF Tokens

Our next problem is that the domain of the PWA may not match the domain of the API (if they are hosted in the same server-side web service then this is an easy problem to solve), and so we have a cross site request forgery (XSRF or CSRF) problem. The main defence against this is the “synchronised token” approach. This is implemented, for example, in the Asp.Net RequestValidationToken approach. Microsoft recommend sending and receiving the CSRF token between the PWA and web API using a secure cookie (not HTTP only in this case). Essentially this gives us an overall more secure solution by using both a secure HTTP only cookie for the auth token and a secure Javascript-readable cookie for the CSRF token.

Obviously this gives rise to a cross-site scripting vulnerability with the CSRF token which should be defended against using the normal mitigations. A combination of good cross-site scripting hygiene, a secure HTTP only cookie for authentication and a CSRF token is a good combination for building a secure eco-system for your PWA and web API.

Indirect All API Calls

Another excellent solution to the above quandary is to simplify web API access by constructing your PWA in such a way that ALL web API calls are ONLY to the domain that hosts both the PWA and the web API. This means that you will be able to take advantage of the “same site” capability of modern browsers and cookies, although this cannot be 100% relied upon due to older browsers still being in use. It also means that you can perform some server side programming to include the CSRF in the HTML served up for the client-side app rather than sending it around in cookies.

In this scenario, all access to third party APIs will be managed by the server-side component of the PWA. This allows much more secure methods for web API credential storage to take place on the server side rather than insecurely on the client side. This is very useful when accessing a web API that requires the passing of an OAuth token in a header, for example.

Notice: JavaScript is required for this content.

Nick McKenna

Nick McKenna is a (polymath) computer programmer and scaled Agile consultant. Nick has been a professional programmer for over 20 years and an Agile guru for nearly as long! Nick's specialities include progressive web app development, mobile app development, the Internet Of Things, Azure cloud development, systems integration, Scaled Agile Framework, Scrum, Lean, LeSS, Scrum At Scale and much much more.