Is it safe to store a JWT in localStorage with ReactJS?
Asked Answered
F

15

333

I'm currently building a single page application using ReactJS.

I read that one of the reasons for not using localStorage is because of XSS vulnerabilities.

Since React escapes all user input, would it now be safe to use localStorage?

Flatulent answered 23/5, 2017 at 11:28 Comment(10)
prefer Session StorageSherris
"Don't store tokens in local storage" - Auth0Murielmurielle
"It's recommended not to store any sensitive information in local storage." -OWASP "store them in memory without any persistence" -Auth0Colpotomy
I think Auth0 might have changed their perspective on this - because I can't find the above quote in the provided linkTafoya
To be fair, @DauleDK, I think that particular quote is missing not because Auth0 now thinks that it is safe to do so, but quite the opposite -- If you read that page, it now appears that they advise that the token never be persisted in a client-side only solution, regardless of the persistence technique used: "If you have a SPA with no corresponding backend server, your SPA should request new tokens on login and store them in memory without any persistence. To make API calls, your SPA would then use the in-memory copy of the token."Votaw
That's a fair point @Votaw - if storing JWT's in a SPA care must be taken. But the real question seems to be if cookies should have been used, as a better alternative.Tafoya
@Votaw The problem with using in-memory token is that it on page refresh, the in-memory cache is deleted. So a user has to log in again which is a bad user experience i supposeOmeara
@haneSmitter SPA implies lack of page refreshingGonfanon
@Gonfanon Well with the freedom on the frontend, any action is possible from the user side. For example a page refresh may occur when the user opens the same page on a new tab. It would be annoying to request the user to login againOmeara
@Votaw The in-memory with low persistance can simply be a sessionStorage. Which resists page reload and tab duplication. The sessionStorage only erases when client closes all the tabs of that "domain". Because storing in-memory in a javascript local variable or sessionStorage is the same concerning XSSSpeculator
I
291

In most of the modern single page applications, we indeed have to store the token somewhere on the client side (most common use case - to keep the user logged in after a page refresh).

There are a total of 2 options available: Web Storage (session storage, local storage) and a client side cookie. Both options are widely used, but this doesn't mean they are very secure.

Tom Abbott summarizes well the JWT sessionStorage and localStorage security:

Web Storage (localStorage/sessionStorage) is accessible through JavaScript on the same domain. This means that any JavaScript running on your site will have access to web storage, and because of this can be vulnerable to cross-site scripting (XSS) attacks. XSS, in a nutshell, is a type of vulnerability where an attacker can inject JavaScript that will run on your page. Basic XSS attacks attempt to inject JavaScript through form inputs, where the attacker puts <script>alert('You are Hacked');</script> into a form to see if it is run by the browser and can be viewed by other users.

To prevent XSS, the common response is to escape and encode all untrusted data. React (mostly) does that for you! Here's a great discussion about how much XSS vulnerability protection is React responsible for.

But that doesn't cover all possible vulnerabilities! Another potential threat is the usage of JavaScript hosted on CDNs or outside infrastructure.

Here's Tom again:

Modern web apps include 3rd party JavaScript libraries for A/B testing, funnel/market analysis, and ads. We use package managers like Bower to import other peoples’ code into our apps.

What if only one of the scripts you use is compromised? Malicious JavaScript can be embedded on the page, and Web Storage is compromised. These types of XSS attacks can get everyone’s Web Storage that visits your site, without their knowledge. This is probably why a bunch of organizations advise not to store anything of value or trust any information in web storage. This includes session identifiers and tokens.

Therefore, my conclusion is that as a storage mechanism, Web Storage does not enforce any secure standards during transfer. Whoever reads Web Storage and uses it must do their due diligence to ensure they always send the JWT over HTTPS and never HTTP.

Ivett answered 26/5, 2017 at 19:49 Comment(19)
So if I'm understanding you correctly, you recommend cookies? Just to make sure. Thanks!Ferebee
Yes. I recommend cookies because of the additional security they provide, and the simplicity of protecting against CSRF with modern web frameworks. Web Storage (localStorage/sessionStorage) is vulnerable to XSS, has a larger attack surface area, and can impact all application users on a successful attack.Ivett
I think you have these mixed up? Modern web frameworks have strong built in defenses for XSS. But not so much for xsrf. The best defense for xsrf is to avoid using cookies completely. Local storage is sandboxed to a specific domain, which means an attackers domain can't access it. Web frameworks defend against xss by automatically encoding and sanatizing user input. See angular.io/guide/securityEncipher
If "you recommend cookies [instead]", then, can I recommend you say that somewhere in the answer? Rather than just in the comments?Junior
@mikejones1477: As the answer says "Modern web apps include 3rd party JavaScript libraries" which means you can never be 100% sure that you're defended against XSS attacks, or which means you can't be sure that your localStorage can't be accessed by an attacker. The best way to stay secure is to store your token in the cookie and implement a CSRF protection (which most backend frameworks offer nowadays).Vacate
I'm here a bit late, just reading these topics now and I'm confused about one thing, a lot of people talk about u're protected with an http only cookie if you are comprimised with Xss, but, if you have xss the atacker does not need to steal you anything, he can simple make a post from the page to impersonate you using that cookie (even if he cant steal it). Am I missing something???Banter
@Borja Alvarez .. Yes you are that making of a post is protected by usage of CSRF protection. Without it yes you are right.Naraka
@Naraka With or without CSRF protection, if you fall victim to XSS the attacker can send a post request. If they can run their code on your site they have no need to send the request cross site. I'm all in favour of storing JWT in a HttpOnly secure flag cookie, because why not? But really, if an XSS attack gets through the attacker can do what they like, send post requests, steal passwords, etc. The fact they can't read the JWT is small comfort.Affidavit
@BorjaAlvarez I agree if an attacker can run their code on your site then they would be able to do harm. But what they will not be able to do is read your JWT token. So they will always need your site up and running to do what they want to do. So in a way they will be at the mercy of your site. But if they are able to read your JWT token then its a party because you (server, company) won't ever know that its not the user who is sending requests because they are sending a valid JWT-token. And yes CSRF protection protect you only when your site is XSS safe.Naraka
You're not stuck with "a total of 2 options." If you must store tokens in your user agent (which you shouldn't be doing), then store them in memory without persistence. That's Auth0's recommendation (auth0.com/docs/security/store-tokens#if-no-backend-is-present) OWASP specifically advises against storing tokens in web storage: cheatsheetseries.owasp.org/cheatsheets/…Colpotomy
If you store your token in memory what happens if the user refreshes the page? Do you ask them to login again? Also what about open one of the pages in a new tab?Ringlet
Is CSRF relevant here? I don't think so. Just XSS should be the point of interest here. And if an XSS attack is allowed, a session can be hijacked even if cookies are in use. Please correct me if I'm wrong. So in my opinion, localStorage is as secure as the traditional cookies.Exogamy
You can store the token in cookies (to avoid XSS) and read it using javascript and send it in the headers (to avoid CSRF). Got it from here. security.stackexchange.com/a/201228Unreality
Clearly the best solution is to store your creds in both localStorage and cookies. That way the cookie is protected from XSS and the Web Storage is protected from CSRF! CHECKMATE! (This is sarcasm. Don't do it.)Despoliation
@Unreality You cannot do them both at the same time. If your cookie is httpOnly you can't read it with js. If it's no httpOnly, attacker can read it as well.Steamer
If there is a successful XSS attack there is nothing you can do to ensure your user's security. IF the attacker can run javascript they can record your users input with event handlers (this cannot be stopped at this point), allowing them to get ahold of the users credentials, which is actually much worse than if they were to simply get ahold of the web token because now they can use those credentials to login to the users accounts on other application (probably). Preventing an XSS attack in the first place should be the goal, preparing for an attack is futile.Rosenbaum
what if we encrypt the JWT token with a secret key and then store it in the local storage it will provide an extra layer to the security, and then we decrypt it where ever we need it.Ogee
@SabaoonBedar How do you decrypt that data client side without exposing the private key in the client side code that hackers can read?Overcast
(Please don't read this comment if you don't have time.) In the end, I conclude that there's no way of escaping the attackers, all our efforts are just in vain. And we only have the choice of choosing the way we want to be cooked! ;DTlaxcala
E
74

Basically it's OK to store your JWT in your localStorage.

And I think this is a good way. If we are talking about XSS, XSS using CDN, it's also a potential risk of getting your client's login/pass as well. Storing data in local storage will prevent CSRF attacks at least.

You need to be aware of both and choose what you want. Both attacks it's not all you are need to be aware of, just remember: YOUR ENTIRE APP IS ONLY AS SECURE AS THE LEAST SECURE POINT OF YOUR APP.

Once again storing is OK, be vulnerable to XSS, CSRF,... isn't

Ensoul answered 1/6, 2017 at 23:5 Comment(9)
This is why it is secure to do the following: - Store the JWT in a cookie so that it can't be retrieved from XSS - Store a CSRF token in localStorage so it can't be retrieved from CSRFCarollcarolle
You bring up a good point: if your site runs a malicious script, it's game over anyway. They can just bind keydown events to inputs of type password and steal your user's authentication information that way (which is much, much worse than stealing a JWT auth token). Storing JWTs in localStorage does little to increase the already immense possible damage from XSS.Land
Unless auth method is single use code, or passwordless or 2FAArticular
When going for localStorage. I suggest adding Content-Security-Policy headers as an extra security layer to minimize the risk of packages trying to steal your tokens.Broomstick
Nowadays, just set Cookie SameSite as strict to prevent CSRF.Curran
@Land This comment convinced me that worrying about what happens when an XSS attack happens is probably not a good use of my attention. If an XSS attack is successful, nothing you can do will protect your users. So, to spend time trying to defend yourself after a compromise is a waste of effort, it's much better to focus on preventing the XSS attack in the first place.Rosenbaum
@Rosenbaum XSS attacks aren't the only thing to worry about. A malicious library or cdn script could steal your access token from localstorage. Keeping it in an httpOnly cookie prevents all javascript from stealing your token.Nausea
Being vulnerable to XSS generally is not something you know in advance. Your application should be as safe as possible to protect it as much as possible, even if an unknown vulnerability exists. We do this for the exact same reason as why we store hashed and salted passwords in a database, rather than plain-text.Aristippus
So I think the solution is to break the chain in 2 points. To avoid CSRF because of cookies, then dont provide a total functional cookie, but the request should need a csrf_token that is stored in localStorage. This way the JTW by its own make nothing, this prevents CSRF. Then In case of XSS it can read the csrf_token but not the JTW token as it is httpOnly secure. But of course in case of XSS everything is bad, any type of data collection is possible... So the first thing should be prevent XSS as much as possible.Speculator
P
64

I know this is an old question but according what @mikejones1477 said, modern front end libraries and frameworks escape the text giving you protection against XSS. The reason why cookies are not a secure method using credentials is that cookies doesn't prevent CSRF when localStorage does (also remember that cookies are accessible by JavaScript too, so XSS isn't the big problem here), this answer resume why.

The reason storing an authentication token in local storage and manually adding it to each request protects against CSRF is that key word: manual. Since the browser is not automatically sending that auth token, if I visit evil.example and it manages to send a POST http://example.com/delete-my-account, it will not be able to send my authn token, so the request is ignored.

Of course httpOnly is the holy grail but you can't access from reactjs or any js framework beside you still have CSRF vulnerability. My recommendation would be localstorage or if you want to use cookies make sure implemeting some solution to your CSRF problem like Django does.

Regarding with the CDN's make sure you're not using some weird CDN, for example CDN like Google or bootstrap provide, are maintained by the community and doesn't contain malicious code, if you are not sure, you're free to review.

Physiological answered 21/4, 2018 at 0:50 Comment(3)
Not sure why you would say you're still vulnerable to CSRF while using cookies. Using a cookie with the flags HttpOnly SameSite=strict and secure, will keep the info you set in the cookies safe. Then against XSS, you simply make sure your JavaScript isn't aware of any authentication related data, like tokens and passwords (meaning, don't store them in Web Storage) - if you import a malicious script, that script will not have access to sensitive data. Yes, you will not have access to the token through JS either, but that really shouldn't be a problem.Dank
@Dank that's what i said. But the OP is asking for a way to access from javascript. Here i'm just explaining what is the best way to store a token accessible from js.Physiological
@miphe, SameSite isn't SameOrigin.Paratuberculosis
S
19

I’m disturbed by all the answers that suggest not to store in local storage as this is susceptible to an XSS attack or a malicious library. Some of these even go into long-winded discussions, even though the answer is pretty small/straightforward, which I’ll get to shortly.

Suggesting that is the equivalent of saying “Don’t use a frying pan to cook your food because if you end up drunk one night and decide to fry, you’ll end up burning yourself and your house”. If the jwt gets leaked due to an XSS attack or malicious library, then the site owner has a bigger problem: their site is susceptible to XSS attacks or is using a malicious library.

The answer: if you’re confident your site doesn’t have those vulnerabilities, go for it.

Ref: https://auth0.com/docs/security/data-security/token-storage#browser-local-storage-scenarios

Summers answered 9/10, 2021 at 3:54 Comment(4)
I don't agree with this perspective which seems to be saying that if you're vulnerable to this attack then it's game over anyways. If you keep your access token outside of javascript, then your access tokens can't be stolen from malicious javascript. This limits the damage that can be done by an attacker. Maybe they're not just trying to steal your access token but are stealing your keyboard input to try and get your passwords or secret question answers. But maybe you're using Oauth and they aren't entering that info into your site to begin with. The point is to reduce the potential damage.Nausea
Agreeing with @jacob. Additionally, you can never be fully confident your site does not have vulnerabilities so it's always good to proactively prevent as much damage as possibleAristippus
"The answer: if you’re confident your site doesn’t have those vulnerabilities, go for it." This doesn't seem like sound advice. Nobody can be confident that they don't have vulnerabilities because new vulnerabilities are discovered every day. Also, isn't one of the best practices in security to assume that you have vulnerabilities and to engineer your systems in a way that mitigates the risk of data getting into the wrong hands? Using Same-Site HTTPOnly cookies seems like the most secure option.Idona
What all these comments are really saying: better to safeguard against your own vulnerabilities as a developer - much easier than understanding fundamentals about how an XSS attack happens and what is required of your site in order for it to even be possible. <sarcasm>Who cares, just show a cookie notification, not bad UX at all, every other site does it.</sarcasm>Summers
E
15

One thing to keep in mind is whether the JWTs are:

  • First party (ie. simply for accessing your own server commands)
  • Third party (ie. a JWT for Google, Facebook, Twitter, etc.)

If the JWT is first-party:

Then it doesn't matter that much whether you store the JWT in local storage, or a secured cookie (ie. HttpOnly, SameSite=strict, and secure) [assuming your site is already using HTTPS, which it should].

This is because, assuming an XSS attack succeeds (ie. an attacker was able to insert Javascript code through a JS dependency that is now running on all visitor browsers), it's "game over" anyway; all the commands which were meant to be secured by the "JWT token verifications", can now be executed by the attacker just by having the script they've inserted into the frontend JS call all the needed endpoints. Even though they can't read the JWT token itself (because of the cookie's http-only flag), it doesn't matter because they can just send all the needed commands, and the browser will happily send the JWT token along with those commands.

Now while the XSS-attack situation is arguably "game over" either way (whether local-storage or secured cookie), cookies are still a little better, because the attacker is only able to execute the attacks if/when the user has the website open in their browser.

This causes the following "annoyances" for the attacker:

  1. "My XSS injection worked! Okay, time to collect private data on my boss and use it as blackmail. Dang it! He only ever logs in while I'm here at work. I'll have to prepare all my code ahead of time, and have it run within the three minutes he's on there, rather than getting to poke around into his data on the platform in a more gradual/exploratory way."
  2. "My XSS injection worked! Now I can change the code to send all Bitcoin transfers to me instead! I don't have any particular target in mind, so I don't need to wait for anyone. Man though, I wish I could access the JWT token itself -- that way I could silently collect them all, then empty everyone's wallets all at once. With these cookie-protected JWTs, I may only be able to hijack a few dozen visitors before the devs find out and suspend transfers..."
  3. "My XSS injection worked! This'll give me access to even the data that only the admins can see. Hmmm, unfortunately I have to do everything through the user's browser. I'm not sure there's a realistic way for me to download those 3gb files using this; I start the download, but there are memory issues, and the user always closes the site before it's done! Also, I'm concerned that client-side retransfers of this size might get detected by someone."

If the JWT is third-party:

In this case, it really depends on what the third-party JWTs allow the holder to do.

If all they do is let someone "access basic profile information" on each user, then it's not that bad if attackers can access it; some emails may leak, but the attacker could probably get that anyway by navigating to the user's "account page" where that data is shown in the UI. (having the JWT token just lets them avoid the "annoyances" listed in the previous section)

If, instead, the third-party JWTs let you do more substantial things -- such as have full access to their cloud-storage data, send out messages on third-party platforms, read private messages on third-party platforms, etc, then having access to the JWTs is indeed substantially worse than just being able to "send authenticated commands".

This is because, when the attacker can't access the actual JWT, they have to route all commands through your 1st-party server. This has the following advantages:

  1. Limited commands: Because all the commands are going through your server, attackers can only execute the subset of commands that your server was built to handle. For example, if your server only ever reads/writes from a specific folder in a user's cloud storage, then the attacker has the same limitation.

  2. Easier detection: Because all the commands are going through your server, you may be able to notice (through logs, sudden uptick in commands, etc.) that someone has developed an XSS attack. This lets you potentially patch it more quickly. (if they had the JWTs themselves, they could silently be making calls to the 3rd-party platforms, without having to contact your servers at all)

  3. More ways to identify the attacker: Because the commands are going through your server, you know exactly when the commands are being made, and what ip-address is being used to make them. In some cases, this could help you identify who is doing the attacks. The ip-address is the most obvious way, though admittedly most attackers capable of XSS attacks would be aware enough to use a proxy.

    A more advanced identification approach might be to, say, have a special message pop up that is unique for each user (or, at least, split into buckets), of such a nature that the attacker (when he loads up the website from his own account) will see that message, and try to run a new command based on it. For example, you could link to a "fake developer blog post" talking about some "new API" you're introducing, which allows users to access even more of their private data; the sneaky part is that the URL for that "new API" is different per user viewing the blog post, such that when the API is attempted to be used (against the victim), you know exactly who did it. Of course, this relies on the idea that the attacker has a "real account" on the site alongside the victim, and could be tempted/fooled by this sort of approach (eg. it won't work if the attacker knows you're onto him), but it's an example of things you can do when you can intercept all authenticated commands.

  4. More flexible controlling: Lets say that you've just discovered that someone deployed an XSS attack on your site.

    If the attackers have the 3rd-party JWTs themselves, your options are limited: you have to globally disable/reset your OAuth/JWT configuration for all 3rd-party platforms. This causes serious disruption while you try to figure out the source of the XSS attack, as no one is able to access anything from those 3rd-party platforms. (including your own server, since the JWT tokens it may have stored are now invalid)

    If the JWT tokens are instead protected in http-only cookies, you have more options: You can simply modify your server to "filter out" any reads/writes that are potentially dangerous. In some cases added this "filtering" is a quick and easy process, allowing your site to continue in "read-only"/"limited" mode without disrupting everything; in other cases, things may be complex enough that it's not worth trusting the filter code for security. The point though is that you have more options.

    For example, maybe you don't know for sure that someone has deployed an XSS attack, but you suspect it. In this case, you may not want to invalidate the JWT tokens of every user (including those your server is using in the background) simply on the suspicion of an XSS attack (it depends on your suspicion level). Instead, you can just "make things read-only for a while" while you look into the issue more closely. If it turns out nothing is wrong, you can just flip a switch and re-enable writes, without everyone having to log back in and such.

Anyway, because of these four benefits, I've decided to always store third-party JWTs in "secured cookies" rather than local storage. While currently the third-party JWTs have very limited scopes (such that it's not so big a deal if they are stolen), it's good future-proofing to do this, in case I'd like my app to request access to more privileged functionalities in the future (eg. access to the user's cloud storage).

Note: The four benefits above (for storing third-party JWTs in secured cookies) may also partially apply for first-party JWTs, if the JWTs are used as authentication by multiple backend services, and the domains/ip-addresses of these other servers/services are public knowledge. In this case, they are "equivalent to third-party platforms", in the sense that "http-only cookies" restrict the XSS attacker from sending direct commands to those other servers, bringing part of the benefits of the four points above. (it's not exactly the same, since you do at least control those other servers, so you can activate read-only mode for them and such -- but it'll still generally be more work than making those changes in just one place)

Eldwin answered 16/7, 2021 at 12:46 Comment(0)
U
14

Localstorage is designed to be accessible by javascript, so it doesn't provide any XSS protection. As mentioned in other answers, there is a bunch of possible ways to do an XSS attack, from which localstorage is not protected by default.

On the other hand, cookies have security flags which protect from XSS and CSRF attacks. HttpOnly flag prevents client side javascript from accessing the cookie, Secure flag only allows the browser to transfer the cookie through ssl, and SameSite flag ensures that the cookie is sent only to the origin.

So cookies are a more secure choice for storing authentication data.

Unsought answered 28/5, 2017 at 19:23 Comment(6)
cant get: how HttpOnly can protect you from CSRF ?Ensoul
@AlexLyalka Didn't mean to say that HttpOnly prevents from CSRF, rather than all cookie flags together can protect from XSS and CSRF. SameSite provides some protection, preventing the cookies from being sent to a site different from origin. Although I just checked and support for that flag is very low. It's also possible to avoid CSRF with a separate encrypted token with some user identification, which is checked on server.Unsought
Well, if someone can execute code in your web, cant he simply make a post to ur web in the name of your user? Ok, he cant get your http only cookies, but he can make calls using those cookies, so I still cant see the pointBanter
@BorjaAlverez There's a big difference. Yes, via XSS someone could make requests on behalf of logged-in user, but compromising a token is worse. For example: the token may provide access to APIs that the client application doesn't use; the token may have other information about the user (email address, profile, and grants); the token could be used in replay attacks against your application; the token could be passed as an id_token_hint to an OIDC auth server; the token provides an attacker information about the cipher that was used to sign it; etc.Colpotomy
What sense has to store jwt in cookie? If then in my web app I would like to request in another server service. That service can't decrypt that cookie in order to get jwt.Liba
You may edit the answer. All major browsers support SameSite flags now. developer.mozilla.org/en-US/docs/Web/HTTP/Headers/…Tlaxcala
B
12

A way to look at this is to consider the level of risk or harm.

Are you building an app with no users, POC/MVP? Are you a startup who needs to get to market and test your app quickly? If yes, I would probably just implement the simplest solution and maintain focus on finding product-market-fit. Use localStorage as its often easier to implement.

Are you building a v2 of an app with many daily active users or an app that people/businesses are heavily dependent on. Would getting hacked mean little or no room for recovery? If so, I would take a long hard look at your dependencies and consider storing token information in an http-only cookie.

Using both localStorage and cookie/session storage have their own pros and cons.

As stated by first answer: If your application has an XSS vulnerability, neither will protect your user. Since most modern applications have a dozen or more different dependencies, it becomes increasingly difficult to guarantee that one of your application's dependencies is not XSS vulnerable.

If your application does have an XSS vulnerability and a hacker has been able to exploit it, the hacker will be able to perform actions on behalf of your user. The hacker can perform GET/POST requests by retrieving token from localStorage or can perform POST requests if token is stored in a http-only cookie.

The only down-side of the storing your token in local storage is the hacker will be able to read your token.

Bria answered 19/10, 2019 at 19:45 Comment(0)
I
10

Perhaps one should not be too concerned

There's a useful article written by Dr. Philippe De Ryck which gives an insight into the true impact of vulnerabilities particularly XSS.

This article is an eye opener!

In a nutshell, primary concern of the developer should be to protect the web application against XSS and shouldn't worry too much about what type of storage area is used.

Dr. Phillipe recommends the following 3 steps:

  1. Don't worry too much about which type of storage area to use on the client side. Saving an access token in the browser localStorage area will save the developer significant amount of development time!

  2. Review your application for any possibility of XSS vulnerabilities. Perform a thorough code review and learn how to avoid XSS within the scope of your templating framework.

  3. Build a defense-in-depth mechanism against XSS. Learn how you could further lock down your application. E.g. utilising Content Security Policy (CSP) and HTML5 sandboxing.

Remember that once you're vulnerable to XSS then its game over!

Indoeuropean answered 20/9, 2022 at 9:20 Comment(0)
F
9

It is not safe if you use CDN's:

Malicious JavaScript can be embedded on the page, and Web Storage is compromised. These types of XSS attacks can get everyone’s Web Storage that visits your site, without their knowledge. This is probably why a bunch of organizations advise not to store anything of value or trust any information in web storage. This includes session identifiers and tokens.

via stormpath

Any script you require from the outside could potentially be compromised and could grab any JWTS from your client's storage and send personal data back to the attacker's server.

Fishnet answered 26/5, 2017 at 4:26 Comment(3)
If I don't plan on using cdns will it be safe then?Flatulent
The author of the article never made a distinction between XSS on sites served via a CDN or directly from a central server. Wouldn't your explanation here also apply generally, not just for CDNs?Sybille
Any npm package you include is js running on your web.Articular
C
5

I'm coming late to the discussion, but with the advantage of more mature and modern auth protocols like OpenID Connect.

TL;DR: The preferred method is to store your JWT Token in memory: not in a cookie, and not in localstorage.

Details

You want to decouple the responsibility of authenticating users from the rest of the work your app does. Auth is hard to get right, and the handful of teams that spend all their time thinking about this stuff can worry about the details you and I will never get right.

Establish a dedicated Identity Provider for your app, and use the OpenID Connect protocol to authenticate with it. This could be a provider like Google, Microsoft, or Okta, or it could be a lightweight Identity Server that federates to one or more of those other services.

Use the Authorization Code Flow with PKCE to let the user authenticate and get the access token to your app. Use a respected client library to handle the OpenID Connect details, so you can just have the library notify your app when it has a valid token, when a new valid token has been obtained via refresh, or when the token cannot be refreshed (so the user needs to authenticate again). The library should be configured (probably by default) to avoid storing the token at all.

FAQ

What happens when someone refreshes the page? I don't want to make them log in again.

When the app first loads, it should always redirect the user to your Identity Provider. Based on how that identity provider handles things, there's a good chance the user won't have to log in. For example, if you're federating to an identity provider like Google or Microsoft, the user may have selected an option indicating that they are on a trusted device and they want to be remembered. If so, they won't need to log in again for a very long time, long after your auth token would have expired. This is much more convenient for your users.

Then again, if the user indicated they're on a shared device and shouldn't automatically be logged in in the future, you want to force another login: you cannot differentiate between someone who refreshed their browser window and someone who reopened a closed browser and navigated to a page stored in the browser's history.

I don't want to redirect the user to a separate identity provider: I would lose context about what page they're looking at.

It should be possible to configure your OpenID Connect (OIDC) client to use an iframe to log in silently when possible (e.g. if the user has told the identity provider to trust the device they're on), and to use a popup window for an interactive login when interaction is necessary. This can help you avoid losing their unsaved changes if their auth token couldn't be refreshed automatically because their device was put to sleep for a time.

When the user first navigates to your page, the only available context should be the URL path/route they navigated to, and it's relatively straightforward to store that in someplace like localstorage before sending them off to the identity provider, so it can be retrieved when a successful login returns them to your site.

Isn't the Identity Provider using cookies to keep the user logged in? What about CSRF or XSS attacks there?

Those implementation details are specific to the Identity Provider. If they're using cookies, it's their job to implement Anti-CSRF measures. They are far less likely than you are to use problematic third-party libraries, or import compromised external components, because their app only has one job.

Shouldn't I spend my time addressing XSS attacks instead? Isn't it "game over" if someone injects code into my app?

If it's an either/or proposition, and you have reason to believe your app has XSS or Code Injection vulnerabilities, then those should definitely take precedence. But good security involves following best-practices at multiple levels, for a kind of layered security.

Plus, using a trusted third-party library to connect to trusted third-party security providers should hopefully save you time that you would have spent dealing with a variety of other security-related issues.

Calculate answered 26/1, 2023 at 0:20 Comment(2)
Even in-memory stored tokens are not protected from XSS attacks as the malicious code runs in the same context as the application itself. See pragmaticwebsecurity.com/articles/oauthoidc/… for details.Monaghan
@Monaghan I understand that, and my last section addresses the point of that article: proven methods of preventing XSS attacks in the first place should take priority. But you shouldn't assume your security will be perfect, and by not storing the token in local storage you reduce the attack surface somewhat. The experts recommend keeping tokens out of the UI code entirely, but when that's not possible Authorization Code Flow (with PKCE) is still more secure than localstorage. medium.com/@benjamin.botto/…Calculate
A
4

TLDR;

Both work, but using a cookie with httpOnly is way safer than using localStorage, as any malicious javascript code introduced by XSS can read localstorage.

Adjoining answered 26/7, 2022 at 9:53 Comment(3)
This only works when your API runs on the same origin as your SPA. This is usually not the case for third-party APIs.Monaghan
@Monaghan One may also use SameSite Lax. The cookie is sent in same-site requests and GET requests initiated from other sites. In simpler words, cookies are sent with cross-site requests that are initiated by the user, such as clicking a link. The cookie is not sent in cross-site POST requests, providing some CSRF protection for state-changing requests. Lax provides a middle ground, allowing cross-site GET but blocking unsafe CSRF POST requests.Tlaxcala
@Tlaxcala don't do this. Any malicious JavaScript can send the token (with a GET) request to a backend of the malicious script. With this, some attacker would get your token and could do whatever he/she wants. Only use lax if you really know what you are doing.Monaghan
Z
2

Isn't neither localStorage or httpOnly cookie acceptable? In regards to a compromised 3rd party library, the only solution I know of that will reduce / prevent sensitive information from being stolen would be enforced Subresource Integrity.

Subresource Integrity (SRI) is a security feature that enables browsers to verify that resources they fetch (for example, from a CDN) are delivered without unexpected manipulation. It works by allowing you to provide a cryptographic hash that a fetched resource must match.

As long as the compromised 3rd party library is active on your website, a keylogger can start collecting info like username, password, and whatever else you input into the site.

An httpOnly cookie will prevent access from another computer but will do nothing to prevent the hacker from manipulating the user's computer.

Zilvia answered 16/4, 2019 at 21:24 Comment(0)
M
1

Working as solution architect for many long years here is my humble input to this whole discussion.

Storing security tokens such as JWT on the client side acceptable but still a mediocre solution that exposes them to cross-site and jacking attacks of different complexity.

A good security practice is to introduce intermediate steps that are uncommon and unexpected for hackers, especially when they are not aiming at a specific system.

One way to reduce the vulnerability of storing and retrieving tokens on the client side is to apply some simple or complex cryptographic operations to them before and after the process.

A more sophisticated solution is to use indvidual client and server public certificates that work together to provide customized security that goes beyond standard encryption sitting in middle of storage and retrieval or use of these tokens.

Misrule answered 28/4, 2023 at 9:39 Comment(1)
This answer would be better if it had more specific examples.Mckinnie
E
0

I have already answered this one 6 years ago, but, as I can see topic is still relevant, so I will share my thoughts for 2024.

In my opinion you can store JWT in local/session storage if you need, there is nothing bad doing it by itself. Anyway you need to consider risks of using browser storage, and handle a lot of things manually.

In another hand you can use cookies to do the job for you. In most of aspects, as it was already mentioned here, it is the best approach, if you don't have something special that can't be achieved using cookies(even hard to imagine what it could be). I can imagine a lot of scenarios that is ,almost, or, impossible to implement with browsers storage like proper SSO flows.

The key here is PROPERLY CONFIGURED CSP and cookie policies, I believe.

If your app is XSS vulnerable, there is no difference in storage. Attacker can just inject the script to mimic your login page and grab user credentials, for example. And exposed credentials is even worse then exposed token.

Relaying just on UI framework sanitization (as was stated in question - everything is sanitized) mechanism is bad idea. You should be careful with what you are doing, especially with 3rd party libraries.

Ensoul answered 6/4 at 11:22 Comment(0)
T
-39

It is safe to store your token in localStorage as long as you encrypt it. Below is a compressed code snippet showing one of many ways you can do it.

    import SimpleCrypto from 'simple-crypto-js';

    const saveToken = (token = '') => {
          const encryptInit = new SimpleCrypto('PRIVATE_KEY_STORED_IN_ENV_FILE');
          const encryptedToken = encryptInit.encrypt(token);

          localStorage.setItem('token', encryptedToken);
     }

Then, before using your token decrypt it using PRIVATE_KEY_STORED_IN_ENV_FILE

Tincture answered 25/11, 2019 at 8:0 Comment(11)
@HassanAlthaf you are missing the point here, there will never be a 100% secure proof app, It’s just that, you’re reducing the attack surface and at least env file will not be published on github directly. Also, the bundled code will be obfuscated and mangled, making it hard for the attackers to find them.Tincture
Private key is not supposed to be exposed. You would compromise the entire API.Grizel
From my experience If you did things intelligently and correctly, your private key won't be exposed in your production build, if so, screenshot to support this or even a url.Tincture
Your example will expose the private key with compilation hard coded into the code. That's really unsecure.Thermionic
Encrypting JWTs is for transmitting sensitive data inside of a JWT. A JWT is a user session though, and even encrypted, still represents a valid user session because it is decrypted when it reaches the back-end. If it's bad to put them into localStorage unencrypted, it's just as bad to put them into localStorage encrypted. @Thermionic As long as the secret is accessed by invoking the process.env (which seems to be inferred by the comment in the value), then this value would not be exposed in build artifacts.Putup
i think, even if you encrypt the key. Attackers might use them by attaching it to the link that needs a key.Obduce
@Putup when the frontend code gets compiled the process.env variable will be resolved and be put into the code. If it wouldn't be resolved, the browser wouldn't know about his env variable. So his private key is exposed this way.Thermionic
@Thermionic I guess I would never imagine sending a private key to the client, and somehow didn't notice the localStorage mentioned multiple times (code and comment). Yikes! Yeah - don't do this folks.Putup
If private key is some sort of user password, but then again, browsers already remember user/pwds anywayArticular
@JuanCarrey user password stored securely by the browser is nothing like local storage variable which can be read by any JS executed on that domain.Figone
I mean that there is no need to reinvent de wheel by creating a new password mechanism (private key on the code) when the browsers already store user/passwords for us. @DmitryVerhoturovArticular

© 2022 - 2024 — McMap. All rights reserved.