How to store Access Tokens: Localstorage, Cookies or HttpOnly?
Learning how to store access tokens is one of the most confusing parts of web development, especially when you're just starting out. This article is all about the do's - and don'ts - of storing Access Tokens, with easy, beginner friendly examples.
Access tokens are a hugely important part of any web-app. They contain sensitive information about your user and if someone got their hands on one they would be able to pretend to be someone else very easily. The way you decide to store your tokens is key to stopping someone from doing exactly that.
I initially learned this while building a Serverless Prisma API for Vercel, which I've since open-sourced to the public. It has some great real-world examples of what's talked about in this article, go check it out if you're confused!
How to store Access Tokens: localStorage
LocalStorage is quite possibly the worst way you could choose to store your access tokens. But why?
1. LocalStorage is easily accessible
While this might sound like a positive to you, it's actually a very real security problem. In an age where any webpage could have dozens of dependencies from external scripts or packages, you never really know what code you might be running.
Any script running from your domain will have access to the data you saved to localStorage. This means that if a single one of those scripts gets compromised, they would have easy access to all of your users tokens, giving them complete access to all your accounts...
2. LocalStorage doesn't expire
Cookies can easily be set to expire and be deleted on a specific date. LocalStorage on the other hand does not offer this option. This means anything saved to localStorage will stay there indefinitely until you or the user deletes it. This sucks because you'll need to build a system to refresh your access tokens manually, or your user info might end up being out of date.
How to store Access Tokens: Cookies
A more common pattern to store Access Tokens is manually saving them to cookies from your client code. While this still isn't very secure it's much better than localStorage. In fact, it has some actual applications that httpOnly doesn't cover.
Cookies are still easy to access, but...
Just as with localStorage, any script on your page can easily run document.cookie
and have access to your access token.
Unlike localStorage however, cookies do have an expiration date so they're a lot more useful for authentication purposes. You can easily set a cookie's expiration date, which will force the user to refresh their authentication once it's been deleted.
This will allow you to keep your user's info synced with your database. For example you'll be able to have a list of permissions in the token, which you can always trust to be up to date, essentially cutting down on the number of checks you'll have to do in your API or against your database. And that's a big plus.
Cookies are perfect for cross-site authentication
Thanks to some of Apple's latest changes, httpOnly cookies aren't able to be sent cross-site anymore. This is part of a wider effort on Apple's part to stop cross-site user tracking, and while it's certainly admirable, it's definitely made life worse for web developers using these cookies for their original intention.
Either way, now that Apple blocks third party cookies from automatically being sent, we don't have much choice but to stop using them. This means that for now plain old cookies are the only way to authenticate a user across domains.
How to store Access Tokens: HttpOnly Cookies
HttpOnly Cookies are the gold standard of authentication. They're secure, easy to use and automate a lot of the authentication process which helps remove complexity from the front-end.
What are the advantages of HttpOnly Cookies?
HttpOnly is just a simple flag that can be added along the other Cookie settings which makes your Cookie invisible and inaccessible to client-side Javascript.
How do you set HttpOnly Cookies?
One of the great things about HttpOnly cookies is that a lot of the headaches of setting and managing cookies are removed. This is mostly thanks to the very streamlined way in which they're set:
// In Node.js
res.setHeader('Set-Cookie', [
`accessToken=${securedAccessToken}; HttpOnly; Max-Age=${60000 * 15};`,
])
Thanks to the Set-Cookie
header, when our front-end receives the response from our API the Cookie will be automatically set by the browser without any need to mess with the response itself.
The above is what you'll be able to see in your network tab. Keep an eye out for any warnings but if you don't see any, your Cookie has been successfully set.
How do you send HttpOnly cookies?
You might be wondering how exactly you're supposed to send your authentication token if your Cookie isn't visible by your client-side Javascript. Well good news! It's incredibly easy as the browser handles it for you:
Access-Control-Allow-Credentials: true
By adding this one header to your request, the browser will send any HttpOnly cookies associated with the domain you're sending your query to on its own.
In the popular axios package this is can be done with:
axios.get('some api url', { withCredentials: true });
A quick recap
Here's a tl;dr of the above:
- Never ever use localStorage to store your Authentication Tokens.
- Always strive to use HttpOnly Cookies for Authentication Tokens.
- In case HttpOnly Cookies fail then fall back on normal Cookies.