Secure Authentication for JavaScript Apps

How to avoid being the subject of the next big data breach.

Tyler Hunt
Insightful Software

--

SPA and JWT Sitting in a Tree

In recent years, as web development has moved towards a more JavaScript-heavy front end, and SPAs and PWAs have become as commonplace as APIs, another TLA has popped up: JWTs. Our tried-and-true server-side authentication schemes tend to fall down when placed into the context of a client-side web app, since pushing a global API key to the client would present some serious security concerns. In light of this, JSON Web Tokens are often presented as the solution.

JWTs attempt to fulfill the authentication requirement by sending a token to the client that provides some assurance via a signature that the data within it hasn’t been tampered with. The token can then be passed back to the server with each API request, acting as the keys to the kingdom, while also allowing “claims” to be included within the token for profile info, user roles, or anything else you like. In this way, the JWT often acts as the session store for the application.

There are lots of reasons to like this approach: it’s simple to understand (“Look, it’s JSON!”), easy to implement (“Oh, there’s a library for this…”), based on an industry standard, and designed for the Web. Combined with localStorage, which is about the easiest API you could ask for to store data like JWTs, it all makes for a very convenient package for developers. And, to back it up, there are many blog posts documenting this strategy.

Given all this, it’s clear why so many reach for JWTs for API authentication. Their sexiness is hard to resist. However, much has been written about their shortcomings, especially when used as a session store. They give the illusion of security while ignoring decades of research and practical advice. There is a time and place where using a JWT makes sense, but it’s not as an API key and it’s not as a session store.

Let’s look at some of these issues in more detail.

The Problems with JWTs

One of JWT’s core flaws is that the token itself specifies which algorithm to use for verification. Early on, there were critical vulnerabilities in many JWT libraries allowing an attacker to specify the none algorithm, effectively bypassing the signature verification step entirely. Even if those flaws have been fixed, specifying the algorithm in the token header is still problematic, as it results in an additional attack vector.

Another issue comes with using JWTs as a session store: you can’t invalidate sessions. For example, you may want to invalidate all existing sessions after a user changes his password. This is possible if the session is stored on the server, since you have access to that data, but a JWT will live on until its expiration. You can’t invalidate JWT sessions without resorting to tracking them on the server, which defeats the point of using them in the first place.

Some argue that pushing the session storage to the client facilitates horizontal scaling. In reality, for all but the largest of sites, this is a non-issue:

It is overwhelmingly likely that you do not [need] crazy horizontal scaling schemes at all, and that a single server-side session management system is sufficient for your use case.

Basically, you’re not Google or Facebook or even Reddit, so YAGNI.

And, despite what the standard says about JWTs being “intended for space constrained environments such as HTTP Authorization headers and URI query parameters”, their Base64 encoding can make them too large to be stored in a cookie or transmitted in a URL. As a result, many people resort to shoving them into localStorage. For something as sensitive as an API token, this is a fairly ridiculous thing to do, as we’ll see next.

The Problem with localStorage

The gist of the issue here is that localStorage has next to no security. While its access is limited to code running on the domain for which the data was stored, there are no restricitions in place that would prevent external scripts running on your domain from accessing it. This opens you up to XSS attacks. If a third-party script is compromised, it could result in anything stored in localStorage being stollen. If a JWT is among the data in that storage, you could have a serious breach on your hands.

Unlike cookies, which provide mechanisms to prevent XSS attacks, localStorage currently provides no such solution. Any script executing on your site will have access to all the data in the store:

Web Storage is not secure storage. It is not “more secure” than cookies because it isn’t transmitted over the wire. It is not encrypted. [This] is not a place to keep session or other security tokens.

It really shouldn’t be used for anything but caching publicly accessible data or storing other non-sensitive data like site preferences.

A Better Solution

So what should we use instead? They may not be newfangled or come with their own acronyms or initialisms, but cookies are currently your best bet. Most web frameworks provide a session store based on cookies right out of the box. In some cases, the cookie will contain the session itself in an encrypted form, and in others the cookie will merely store an identifier that’s used to look up the session on the server.

Web framework session stores have long relied on these cookies for identifying users. While they’ve had their own share of security flaws over the years, at this point cookie implementations are well-tested and hardended from a security standpoint. Additionally, because support is already built-in at the library and framework level, there’s no need to figure out your own implementation.

Cookies allow for a variety of flags to be set that limit their accessibility. The Secure flag prevents cookies from being sent to the server unless the connection is encrypted, and HttpOnly prohibits the contents of the cookie from being read through JavaScript at all, eliminating the possibility of XSS attacks. When using a session based on a properly-secured cookie, the flaws and vulnerabilities outlined above become non-issues.

And for server-side sessions, their centralized nature means they can be invalidated as needed to support new features or handle security incidents. They also enable features to be developed that would allow users to review their recent logins and invalidate sessions themselves should the need arise.

While it’s tempting to go with the flow and take the widespread advice to use JWTs in localStorage for your client-side apps, don’t do it. It’s not worth the risk to your users, it’s not simpler to implement, and it won’t make you cool no matter how many Medium posts you write about it.

--

--