Should you put JWT in a cookie or local storage?
I am not sure if I should put the JWT I use for authentication in the response body or in a HTTP-Only and secure cookie. Which one is better for security and why?
Please note that if I talk about JWTs in the following post, I mean JSON Web Signature Tokens and not JSON Web Encryption tokens.
Why using cookies is more secure than storing your token in local storage
A browser in general is a very unsafe place. If you store the JWT in your local storage, then malicious scripts or browser extensions would be able to steal it.
When you do store the JWT in the cookie, make sure that you have the following security mechanisms in place:
Secure Cookie Attributes
Make sure that the cookie you set contains the following attributes
- Http-Only: this makes sure that no JS can read the cookie
- Secure: this makes sure your cookie is only transferred over a secure connection
- SameSite: Lax or Strict: this provides protection against CSRF attack vectors like hidden forms and so on. You should never use None as a value
Proper CORS Policy
CORS itself is not a security “thing” itself, but rather a way to relax the very strict Same Origin Policy. If you run a Single Page Application, you will not get around CORS anyway because you will probably make requests to a cross origin (your api gateway hosted on a different subdomain) anyway.
Make sure that you do not use the wildcard *. This is more common than you might think because developers “just want to make it work”.
Note that with CORS the browser only blocks XHR requests (AJAX calls). So it’s not like it is impossible for a browser to make a cross origin request because you created a proper CORS policy. CORS does not kick in with “classic” form submissions over the server.
However, an attacker could also use a classical form submission (hidden forms) to start a CSRF attack. To prevent this, make sure that you set SameSite to either Lax or Strict. Please note that setting SameSite to Strict might have negative usability implications because if the user navigates from a third party site to your website, the cookie would not be sent along in the initial request and the user would need to log in again.
If your app is completely rendered on the server side, then the most effective way to mitigate CSRF are CSRF tokens.
Content Security Policy to prevent XSS
Regardless of whether you decide to store your token in a cookie or in local storage, you need to take measures to prevent XSS. A Content Security Policy is hard to get right, but protects you against Stored XSS (very dangerous) and reflected XSS.
You can read more in Mozilla’s guide about CSP.
Caveat: JWTs might not be good session tokens for you
Note that using JWTs as session tokens might be unacceptable in terms of security because you cannot log a user out from the server side or change the roles without introducing some sort of state.
JWTs are always a snapshot of the past. If you get a JWT (or a JSON web signature token to be precise) it basically tells you that at the time it was issued, the token was valid and the claims were valid – back then. Things could have changed, the token could have become invalid before its expiration.
Oftentimes people argue that you could implement some cache that holds a blocked list of tokens. Yes you could, but my question is: why would you do that? You are reinventing the wheel because you could have used sessions instead. And if your blocked list cache crashes or is unavailable then the system might grant users access that might have been blocked. If you use sessions and your session storage is down, then your system is in a safe state. Noone can use it, but at least you are not vulnerable to attacks.
Scale is also not really an argument for JWTs as session tokens because for probably 95 % of all companies using a “big enough” session store would be sufficient. In addition if you operate at massive scale, your bottleneck will most probably be your SQL database and not the size of your database session cache (e.g. Redis) because Redis is orders of magnitudes faster than any SQL database.
So if you do not need to log someone out from the server side or you are just doing a hobby project where you don’t want to pay for the additional Redis instance, using a JSON Web Signature tokens might be fine. Please just be aware of the cost. JWT != free lunch.