How to securely store JWT tokens.

George Koniaris on April 11, 2020

Article originally published here. In the last years, JWT tokens are widely used as an authentication and authorization method for web application... [Read Full]
markdown guide

If you use CSP to block any inline-script/insecure-script and also enforce that only trusted JS files must be accepted: there's no issue about using LocalStorage.

The article doesn't make sense for me. Yes, using cookies might make your session-value secure. But, let's suppose that your website is vulnerable by XSS in the first place. The attacker can send a request using that session, without the need to steal the session value itself. The "CSRF protection" doesn't protect against XSS, because the requests are executed inside your own website.


Hello Lucas. I agree that this way you would be protected by scripts that are not hosted in your own domain. But in case there is, for example, a node module that you installed while developing it would be considered insecure. I understand your point and it is 100% valid. But with this approach, even in the rare case that a module was vulnerable and you had by accident injected it in your base app, the module could never send the token to an external domain through an Ajax request. It could only hit your internal API, but this would require that the attacker had injected malicious code in a node module just to attack your web application specifically. Let me know about your thoughts.


I don't know. That issue about leak the session can also be fixed with CSP, since you can block external communications too. I never use node modules, and you might guess why, so I can't say anything about it.

What I'm saying is that it is easy to find solutions when you have such a small attack surface, and never tell it. You can also suggest encrypting the local storage value using a random key using, with a random algorithm with a random name... Yes, attackers can extract the key (...), but then use the same argument: "just to attack your web application specifically". The opposite scenario might be valid: since I'm considering that the page is secure against XSS, then I can use LocalStorage.

I think it would be better if it compares all alternatives (SessionStorage, LocalStorage, Cookies, Credential Management API, IndexedDB API) and all kinds of known attacks.

So you suggest serving external js from another domain and set a CSP to disallow these scripts to access local storage or make ajax requests?

That’s correct. But if you serve external scripts through a CDN for example, and set a csp you are secure with the cookie implementation too. I may write a complementary article or extend this one at some point. Thanks for your feedback its really valuable!!!


That is not true. With javascript you can just create a tag with an src to the attacker sites - and as we know the browser always sends the cookie with the requests - so bingo the attacker now has your cookie. Please beware of that

Hey Milebroke,

Not sure which comment you wanted to leave a reply. If you refer to cookies, in the case of Lax and Strict they won't be sent to the attacker's website if we, for example, inject an image with the attackers URL as the source. That's because it's not considered a first level navigation event.

Although, it may be sent if the attacker creates a GET form and sends it by clicking the button through javascript. I have to admit that I have not tested this scenario, but you will be fine if you just use Strict flag, as it will prevent all cross domain cookie sharing.


Also a small comment, I personally think that the endpoints that are related to payments and generally sensitive account actions should require an extra login with a different authentication method, valid for one API call only (especially for payment API calls) and should be served in a page where only vanilla javascript written by the web application developers is used.


Just some thought, but isn't the method of using Cookies just as vulnerable to XSS as that of using localStorage? I'm new to Web Security and have been wondering this all the time. I mean if an XSS attack happens, the attacker can do whatever they want on behalf of the victim, including maliciously sending requests that have totally valid cookies. The cookie-based authentication, from my understanding, can at best introduce a little bit of inconvenience for the attacker, but at the same time making the authentication process much more complex. Therefore, in the end, what you have to do is to make sure you're not (so) vulnerable to XSS or it is your doom.
Nice post btw, especially the part about SameSite policy stuff, very informative.


Hello there,

Thanks for commenting. Cookies are XSS vulnerable in the case that we don’t set an HttpOnly flag in the server when creating the cookie. When the cookie is set with an HttpOnly flag it can’t be accessed by javascript in any way (at least in modern browsers, I don’t know what happens with ie 7 or 8 :p)


I think @quochuytlbk does not suppose that you want to steal the session, but use the session already active on the page. Let's assume that your website is vulnerable by XSS, then I can inject something like: fetch('api/account', {method: 'DELETE'}). I still don't know your session/cookie, but I manage to use them to delete your account. The fetch call was performed inside your website, it's not CSRF.

Nothing is 100% secure, but that would require that the attacker finds a way to inject code to specifically target my own web application. What you say is 100% valid, my approach is vulnerable to this kind of attack. But it would also be vulnerable if someone was using local storage. I don't know if there is any way that you can prevent this type of attack.

Lucas delivered what I failed to. So yes, that's the point. When an XSS vulnerability is exploited, nothing can save our users. The best bet is to prevent XSS from happening in the first place, or everything is vulnerable.
That being said, why would I go through all the trouble of trying to protect cookies from both CSRF and XSS, just to introduce a little bit of inconvenience to the attacker? At least with localStorage, I don't have to care about protecting against CSRF.
So in my opinion, using XSS as an exclusive drawback of localStorage can be misleading. When I first started to dig into the "localStorage vs Cookies" battle, I unknowingly thought it was "Protecting against XSS vs Protecting against CSRF". That is of course not true. It is "Protecting against XSS vs Protecting against CSRF and XSS".
I think one of the valid use cases of cookies over localStorage (or sessionStorage, IndexedDB, etc.) is SSR (where there is no localStorage).
Edit: Another valid use case of cookies is when you have subdomains because I've heard that localStorage doesn't work across subdomains.

So I have come to my own conclusion that it all depends on your use cases and how you use the methods. There is no "localStorage is totally better than cookies" or vice versa.


first of all it's a really great post! But I have one question in case for example you have separate authentication server you can not use HttpOnly flag right? or in that case I should use just subdomain?


Can you give a specific example of how you authenticate (the login process) and then how the other APIS validate that the user is authenticated?


I was some time ago already so I don't remember all details to be honest, but the gist is frontend redirected user to authentication server (we used keycloak) then we stored token in the local storage and we reissued it like each minute (or something like that) as I remember. Backend used keycloak adapter which again made request to keycloak server to validate the token.

OK, so I guess that backend was also making a request to keycloak (through redirect) and was then returning the token to the user. If that's the case then you just have to set the token in a Secure HttpOnly Strict cookie instead of just returning it, for example as JSON and saving it to the local storage.

hm not exactly authentication happened exactly on keycloak server and then backend made request to keycloak just to validate the token, so user received token from keycloak server and there for I suppose just cookies solution would not work. As I understand that solution could be migrated from local storate to cookies only if we moved keycloak to some subdomain and used subdomain cookies sharing. But definitely I can confuse something

If it follows the oauth2 flow, you could redirect to the backend of your web application instead of the browser, so then the backend would set the cookie and would redirect back to the frontend page.

yeah it would work I think thank you for the answer!!


[cookies] are automatically sent in every browser request […]

Therein exactly lies the reason why people tend to shy away from them. It means blowing up every request by a few (or, in case of, JWTs, many) bytes, even requests that don’t require authentication like images, CSS, scripts…

Of course you could work around that by using a different domain for static assets or by using a single path prefix for all requests that need authentication and then set the Path flag of the cookie to that value but all of these things require infrastructure changes…


Hi Raphael,

Thanks for mentioning this issue. It may not seem like a big deal but in some cases it is. Personally, I try to use cookie-free domains if speed is an important factor for my applications.
I remember reading an article talking about people lining in Africa, who could not even access simple pages because of the tons of modules, CSS and javascript that we use on our websites today. It's pretty sad if you think about it, a big part of today's world doesn't have access to the internet because we don't make our own web applications accessible to non-broadband connections.


Nice post George, I've recently integrated the SameSite cookie policy to one of my Go packages, the iris web framework one (github.com/kataras/iris) and users loved it so I totally encourage web developers to set a cookie policy through SameSite, it's fairly easy-to-understand concept and it looks like 2020


A technique I use is to split the JWT into two cookies. The header + payload accessible through JavaScript (for client-side reading of the claims), and the signature is HTTP only (not accessible through JavaScript). Align cookie expiration with JWT expiration for auto-logout.



I use this.

Header + Payload are stored in LocalStorage and sent in a header with fetch.
Signature is in a cookie with HttpOnly.

The server stitches the header with the cookie and then validates the JWT. This works well in IE 11 which does not support SameSite cookies.


First off, great article! I've recently finished building an authentication system that works very similar to the one you've described but, with a distinct difference: I store the JWT across two cookies.

One cookie contains just the JWT header and payload and can be accessed by JavaScript, the other contains the signature but is Secure + HttpOnly. This way I can access the payload on the client without worrying about having the entire token potentially compromised.

Authentication in SPA the right way by Jean-Cristophe Baey describes this approach in a bit more detail.


Thank you very much. That's a very clever approach!!! I will definitely try it on my next projects.


One thing missing (from most web site authent. tutorials) is: how many tokens/sessions are currently allowed for a single user?
Because whatever the method [in your post], this does not prevent a whole range of issues.
Let's imagine you lend me your computer so that i can reserve a train, but instead go to DevTools and copy the cookie/localStorage/sessionStorage, i'm done in less than 30s and i can reuse the token whenever i want on my computer :)

If you want more security, you need to store a list of currently allowed signatures on server side.
This way you can:

  • check if user is already logged from somewhere else (physical location or other browser for instance)
  • restrict number of simultaneous sessions for a user
  • provide a way to revoke tokens to the user. See for instance in gmail : "Open in x other location · Details" and "Sign out all other Gmail web sessions" button

Note : do not store the whole token, otherwise if your database is compromised, tokens can be used to impersonate anyone...

This can be also completed with:

  • fingerprinting of the device (user agent, extensions...) to trust only some devices and trigger an alert for any untrusted device.
  • brute force prevention if someone tried to login but failed, take exponentially more time to respond (up to a few seconds) this requires thinking when scaling your web app on multiples nodes (ex: keep number of tentatives in a redis instances)

Hey Thibault,

Thanks for sharing all this info. All these things are really useful. Of course, they cannot be covered in a single tutorial, and it would also be out of the scope of the article.

I haven't implemented a solution that is so complex till now. Do you think that JWT tokens are a good solution for those cases? I mean, if you need to perform actions like checking if a user is logged in, JWT automatically loses a big advantage compared to traditional sessions, the ability to authenticate the user without any query to a DB server. Of course, you keep the authentication stateless but this can be achieved with generated ids that identify the user session (stateless session) too. What's your opinion on these subjects?


I'm not experienced enough to give you a definitive answer.

Managing traditional sessions isn't that simple either, especially when you want the "remember me" feature. By implementing things yourself, you're likely to open doors.
Thus, a JWT library with stateless session definitely brings advantages. I also like the encoded payload+signature compared to a lot of things seen in cookies :p

So maybe using JWT and storing only the signature in a table with a foreign key on the user would be interesting.

  • Checks are only made during authentication.
  • Every other http call is stateless

Theeeen what is the point, should I store the token in cookies with Https and only flag?


If you store the cookie with HttpOnly flag, Secure flat and SameSite=Lax|Strict flag you are secure and good to go! This article assumes that you don't need access to the JWT content in the frontend, so it is ok to use the HTTP flag. So by setting the HttpOnly flag, the cookie can not be retrieved from Javascript, and by setting the SecureSite=Lax|Strict it won't be sent in cross-domain requests. Sorry if I was not 100% clear on this point.


Ok, but if i need to get access from the frontend to make some request to my API in the backend, how should I get the token to authenticate my API calls from JS?

If your frontend is on the same domain as your API (or a first-level subdomain), the cookie should be automatically sent through Ajax requests. For example, if your web application is hosted on kevin.dev and your API is hosted on api.kevin.dev, the cookie will be sent in every Ajax request automatically. Of course, you should make your API fetch the cookie in the backend, and verify the token. In case you need to access the payload of the JWT token, that's not possible with HttpOnly cookies. You should make an extra request to your API, something like a /me endpoint, and save the data in a Javascript variable.

Oh :O thnxs a lot, finally a good post to solve my problem!!


"This is achieved by verifying the received token with the exact same key that was used to sign it in the first place" - If I understand this correctly, this would be a symmetric key. It also possible to have an asymmetric key that uses a public and private key. See also stackoverflow.com/questions/329009...


Yes, that's correct. I think it's useful when the client wants to verify that the token was issued by a specific authority. Have you ever used it like this?


Short living jwt token and one-time jwt refresh token will add protection from token stealing. If someone steals an access token - in works for a short time, if someone steals a refresh token, it would log out the current user because his refresh token is no longer valid. When the user logs in again it invalidates the refresh token of the attacker.


Would it be possible/better to split the token between localstorage and a cookie? This would prevent against CSRF and XSS token-stealing attacks at the same time. Part of the token is only accessible by the actual site because it is stored in localstorage, and part of the token in not accessible by JS at all, but could be sent from any site.


Yes, it has been mentioned in other comments too. It’s a nice approach!!


If you are storing your JWT tokens in the cookies because they are secure enough... Why do you need JWT to start with? Just store the data in the cookie, no?


Hi there,

If you store data in a cookie, as JSON for example, how would you validate that the data sent to your server is valid? I mean, everybody could just create a JSON representation of a user, send it through an HTTP cookie and then they would be allowed to performed authorized actions in your web application. But I think that you refer to the case that you want to access the data of the JWT in the frontend.

The examples of these articles assume that you don't need access to the JWT in the frontend (I mean access like getting the full name of the user using the JWT or something like this) and that this kind of JWT is used only for authentication purposes, to make it easier for the backend devs to validate that the user is logged in without performing queries or using sessions in the traditional way. For frontend access to the profile of the user, I prefer to get this data from a /me or /profile endpoint, as it's always up to date and I don't have to mess with decoding JWT in the frontend too.

Does this answer make sense to your question, or did I get something wrong?


"when a user closes their browser, the JWT will disappear" - I believe browsers can also restore sessions these days.


Hi David, this is specific to sessionStorage. If you mean traditional sessions through cookies, of course, they are able to restore them, actually, they never lose them. Based on MDN sessionStorage is cleared when you close the tab or the browser.

code of conduct - report abuse