loading...

LocalStorage vs Cookies: All You Need To Know About Storing JWT Tokens Securely in The Front-End

Michelle Wirantono on July 21, 2020

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.... [Read Full]
pic
Editor guide
 

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.

Modern JavaScript frameworks have pretty good XSS protection built in.

  • OWASP XSS Prevention Cheat Sheet

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.

 

If you use Express, then it could be worth looking at Express Session and the option to save the data to Redis:

app.use(
  session({
    name: 'sessionForApplication',
    secret: process.env.SESSION_SECRET,
    saveUninitialized: true,
    resave: true,
    cookie: {
      expires: expiryDate,
      domain: process.env.APP_DOMAIN
    },
    store: new RedisStore(optionsForRedis)
  })
)
 

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.

 

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 :)

 
 

Very descriptive and helpful article.
Thanks!!!

 

Thanks Jorge!

 

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 :)

 

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 from document.cookie, otherwise you can access the cookie from document.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:

cotter.tokenHandler.getAccessToken()

and it will:

  • grab the access token from memory if not expired, or
  • automatically refreshes the access token by calling Cotter's refresh token endpoint (where the cookie is included) and return to you a new access token.

If you're interested, shoot me a message on Slack and I can help you with any questions. You can find our documentation here.

 

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

 

Very nice article, it's a good read.

 

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.

 

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.

 

Do you think that handling local data on the frontend is better to use GraphQL than Localstorage ?.

 

We could argue that JWT ain't safe anyway.

 

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.

 

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.

 

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.

Code of Conduct Report abuse