JWT Tokens are awesome, but how do you store them securely in your front-end? We'll go over the pros and cons of localStorage and Cookies....
For further actions, you may consider blocking this person and/or reporting abuse
The part of this discussion I always stumble over is when it is recommended to "just" use anti-CSRF tokens. This is a non-trivial requirement. It is easy for one server -- most of them have built-in libs just like with JWT authentication. However, unlike JWT authentication it is a stateful process. So once you go beyond a single API server (including a fail-over scenario) you have to externalize the issued CSRF tokens into something like Redis (or a DB if you don't mind even more added latency). So all servers can be aware of the issued tokens. This adds another infrastructure piece that needs to be maintained and scaled for load. Edit: I guess people already using session servers are thinking "So what, we already have Redis to track user sessions." But with JWT, user sessions are stateless (just the token they provide and you validate) so this extra infrastructure isn't needed. That's a maintenance cost eliminated.
As far as local storage being vulnerable to XSS attacks, OWASP also puts out an XSS Prevention Cheat Sheet. The main attack vector for XSS is when you allow users to directly input HTML/JS and then execute it. Most major frameworks already santize user inputs to prevent this.
The less common threat that you mentioned was NPM libraries becoming subverted to include XSS attacks. NPM has added auditing tools to report this and warn users. (Edit: Fair point is that people sometimes still use JS libs from CDNs, which may have less scrutiny.) And also Content Security Policy is supported in all major browsers and can prevent attacks and the exfil of token/data even if a script on your site gets compromised. It does not necessarily prevent the compromised script from making calls to your own API. But they would have to be targeting your API specifically to accomplish much.
I completely understand the recommendation to use cookies + Secure + HttpOnly + anti-forgery tokens from a security perspective. And as far as I am aware it is superior security to JWT in local storage. But it also has pretty significant constraints. And local storage is not bad, security-wise. It is isolated by domain. XSS attacks are already heavily mitigated by just using a modern JS framework and paying attention to NPM audit warnings. Throw in CSP for good measure. And of course not going out of your way to evaluate user-entered data as HTML/JS/CSS. (If your site functionality requires this, then you probably should use cookie auth and CSP.)
Hi Kasey, thanks for your comment! I do agree that localStorage is not bad at all, and considering how XSS attacks are already heavily mitigated as you mentioned, it's a valid option.
Hey thanks for the response! Best wishes.
Great article. Thanks for the in depth research and clear tutorial. Logic was very concise. 😃
Happy to help! Feel free to ping me if you have any questions/concerns :)
Thanks
Was in a long search for this clarification.
Thanks
Thanks Anshul! Let me know if you want me to discuss any other topics related to Authentication :)
For sure
As for now, this article clears most of the doubts maybe in future if I lost around something related to authentication, will let you know.
Thanks for this article, it helped me a lot!
Thanks Lucien! Let me know if you have any questions :)
If you use Express, then it could be worth looking at Express Session and the option to save the data to Redis:
Yes, redis is the best one🙂, also cookies would be my second option for JWT based storage
Hi Wayne, Putri here – Michelle's cofounder.
This is very helpful, Express Session with Redis is definitely a great option. Thanks for the comment!
A pleasure, and glad to help.
Very descriptive and helpful article.
Thanks!!!
Thanks Jorge!
Hi Michelle, really great article!
What always confused me about httpOnly cookies and JWT is that the frontend app is missing a big benefit of JWT, which is the payload containing claims and possibly other custom data from the backend.
This is most often the user's role, which then the app uses to render privileged parts of the UI and so on, or the token expiry information. With httpOnly, this benefit is not utilised - but the cost in increased packet size is still being paid!
There are strategies which take option 3 to the extreme, and people have already written great articles about this in details, that the JWT token itself should be split into 2 parts, it's signature in httpOnly, and the rest in a normal JS-accessible cookie. This ofcourse increases the complexity of the backend as well, which now needs to piece together the final JWT from two different incoming sources. I guess this could be option 4.
It seems to me, that in order to make good secure use of JWT, considerable complexity on both stacks must be considered. Alternatives are either insecure, or not utilizing the benefits of JWT, which would then just be better off using bearer tokens.
Again, thanks for the great article. It really got me thinking about these things and I think a great discussion could be made about the topic.
What is your take on splitting the token into two cookies? Does the added complexity justify the security gained?
Hi Marko, Putri here – Michelle's cofounder.
That's an interesting suggestion! I don't quite understand how the frontend would miss being able to read the claims/custom data in the JWT using option 3. By storing the access token in memory, you can decode and read the claims in the frontend whenever the access token is available. When the access token is not available in memory (after a refresh/change tab), you can use a function that will refresh the access token, and now you have the access token available again in memory and you can read/decode it in the frontend.
Splitting the JWT might be a useful option if the above solution doesn't help. Let me know what you think :)
By storing the token in memory, you risk compromising it by means of xss. The damage is contained since the token is short-lived, but still a window of opportunity exists.
We can either accept this risk or add considerable complexity to reduce it. What do you think?
That's true, storing in memory is still prone to XSS attack, it's just harder for the attacker to find it than localStorage.
Splitting the JWT into 2 cookies where the signature is in an httpOnly cookie, but the rest of the JWT is accessible to JavaScript makes sense. This means that the frontend can still access JWT except for the signature.
I think it's up to the website to determine what kind of attack factor that they're trying to mitigate against to decide whether they need the upgrade in security.
I just wonder what is actually accessible by
document.cookie
?Secondly would be the implementation. I am interested in all processes from highly-accessible sign-in, to protecting the API endpoint, and the server knows requesters' credentials (for attaching
userId
in database queries). I currently use Firebase / firebase-admin for these reasons, but I have trouble implementing storing token in cookies. I fear that it might be backend dependent...I will consider your product.
Hi Pacharapol!
Cookies that are marked
httpOnly
are not accessible fromdocument.cookie
, otherwise you can access the cookie fromdocument.cookie
.source
With our JS SDK (from
yarn add cotter
), we actually handle storing the access token in memory and the refresh token in the cookie for you. In short, you can just call:and it will:
If you're interested, shoot me a message on Slack and I can help you with any questions. You can find our documentation here.
Nice article, thank you! One thing I'm not sure I totally understood: About "Store your access token in memory and store your refresh token in the cookie". Doesn't that make us again vulnerable to XSS attacks? Because your in-memory token would be available by some injected javascript, no?
Hi, I am so excited about this article,
But what if the refresh token takes more than 4KB?
Is there any way to increase the space of Cookie?
Cookie is reling on the type of Browser?
Hi Pony, refresh tokens are usually opaque random strings stored in your database, so they shouldn't take more than 4KB.
I don't think that there's a way to increase the space, but you might be able to split a large cookie into 2. However some browser limits cookie size per domain, so that wouldn't work.
Here's a nice list about cookie limits per browser browsercookielimits.squawky.net/.
Thank you for your kind support
Love to wait for your next post
Hi Putri,
Just to let you know that the link in your reply is now dead.
I only signed up to leave a comment after landing here from a github issue.
I sent a random email to Kevin from the Cotter team a few months back asking for a feedback on something :) - I'd rather not go too specific..
And he was the one of the most friendliest & smartest person I ever met online - even though we had never met before.
Although our current project does not quite fit the bill for cotter usage(because we're a dApp and we have to use MetaMask for authentication), I'd definitely give cotter a shot if we had a chance. I can only imagine the product would be fantastic if smart, nice and hard-working people like Kevin had been working on it. And from my personal experience, I have learnt that the team matters as much as the product itself when choosing a Sass product.
Disclaimer: I have no ties with the cotter team. Just something out of my personal experience with one of the co-founders at Cotter.
You can use JWT localstorage and prevent CSRF attacks. When you are using a token bearer you are saying to the server that you only allow request with this token from the current browser client, so if a hacker stole the token, he can't make the request because the token are not coming from the original client. JWT is secure and for more security just config the life time of the token less than 8 hours.
This was concise yet effective article to understand the pros and cons of storing JWT in local storage/cookie. But I have a question.
In the firebase auth REST APIs, they are returning both accessToken and refreshToken in the response body. As per Option 3 in your article refresh token shouldn't be inside response. Refresh token should go to Cookie directly so that nobody can access through XSS. Why Google is doing that?
firebase.google.com/docs/reference...
Hi,
Is it possible to have explaination about that part:
I do not understand the following part : "Although a form submit to /refresh_token will work and a new access token will be returned, the attacker can't read the response if they're using an HTML form"
To me, if the attacker manage to inject Javascript Code through XSS that will send an HTTP to /refreshtoken, then he will be able to read the response, thus retrieve the AccessToken and maybe send it to his malicious external server to us it.
Am I missing something? Thanks a lot !
Surely the vulnerability here is that you have a site vulnerable to XSS not the choice of where to store the token?
Hi Will, Putri here – Michelle' cofounder.
Yes, technically if your site is vulnerable to XSS, the attacker can do a lot of damage no matter where you store the token. The options above are intended to help in making it harder for the attacker to obtain the access token itself.
I guess if your website is vulnerable to XSS attack, it's game over anyway 😐 JWT token now doesn't matter. What's your thoughts?
Hi Pankaj, yep I agree with you! It's true that if your site is vulnerable to XSS attack then technically the attacker can do almost whatever they want. However, it is possible to make it harder for the attacker to read/use the access token, which might help in some cases.
Very nice article, it's a good read.
I've a question, if i submit a
/refresh_token
request in the attack code, can I get the user's access token?no, because the refresh token was a httponly, same site cookie unreadable by javascript. If the refresh token cookie is not there /refresh_token should fail.
We could argue that JWT ain't safe anyway.
Wouldn't using sessionStorage be as secure as using variables to store access token? Both are available from JavaScript for the duration of single session.
Hi Jakub, yes they are both available from JavaScript for the duration of a single session. However, it might be easier for the attacker to just dump the contents of the session storage compared to trying to find the variable you used for the token.
Do you think that handling local data on the frontend is better to use GraphQL than Localstorage ?.
Useful article but can you explain why "Option 3...is the best out of the 3 options". Why put the access (bearer) token in web storage and the refresh one in a cookie and not the other way around?
Hi Michelle, thanks for the article.
I'd like to mention one more (possible) con of localStorage. If we create an SSR app then localStorage won't be available for storing tokens.
Straight to the point! Thanks!