10

I am building SPA (React/Redux) and require user authetification. I have found similar discussions, but haven't found answers for questions I outline below. Here are some options I found to implement:


Option 1: Keep JWT in localStorage

CSRF attack: Not vulnerable, as cookies are not used.

XSS attack: Vulnerable. Any JS code in front-end will be able to steal the token and send it to attacker. With Content-Security-Policy enabled, attacked can't steal/send to himself jwt, but still can make request directly from client browser.


Option 2: Keep JWT in cookie (secure and http-only)

CSRF attack: Vulnerable. Client can follow malicious website, which will make sneaky request, cookie attached, requst succeed.

XSS attack: Vulnerable. Cookie can't be read by JS, but through JS attacker can just make requests to valid back-end, cookie automatically attached and all request will succeed.


Option 3: Keep JWT (with xsrf_token set) in cookie + keep xsrf_token in localStorage/JS-readable cookie

Solution is based on this and similar to Double Submit Cookies Method

CSRF attack: Not vulnerable. If client follows malicious website, which makes sneaky request, that request will be dropped, as xsrf_token was not passed from client's browser alongside cookie. And, as cookie is not visible to attacker (comes directly from client browser), he can't make fake xsrf_token to attach with request.

XSS attack: Vulnerable. xsrf_token in localStorage/JS-readable cookie can be read by attackers JS. So, an attacker will be able to make malicious request directly from his injected JS from clients browser.


Originally, I wanted to use option 1. But have been concerned with XSS, so started looking for alternatives.This article and number of others advise against localStorage, and recommend to use Cookie + CSRF protection.

Option 3 seems like the popular solution, from collected data, but still has XSS just like option 1 - injected JS can make malicious requests directly from client browser. The only benefit is that in option 1 JWT encoded data is readable by user/attacker, while in option 3 it's not (as stored in cookie). Related question

Q1. Are there any other benefits of Option 3 over Option 1?

Q2. If I go with Option 3, am I introducing some other attack vectors, that I havent't considered yet?

Q3. Is there any other way of CSRF mitigation, instead of keeping state in front-end (localStorage/sessionStorage/Redux store/JS-readable cookie) which is always vulnerable to XSS?

I know there is no one magic bullet in security. But perhaps there are some other approaches/options to this problem? To remove XSS, you need to go full cookie mode. But to go cookie only, you need to have xsrf_token anyway in user front-end (localStorage/sessionStorage/Redux store/JS-readable cookie), which is XSS vulnerable. This seems like catch-22 problem.

Ilya
  • 205
  • 2
  • 5
  • [Check out OWASP](https://www.owasp.org/index.php/JSON_Web_Token_(JWT)_Cheat_Sheet_for_Java). The have a section titled "Token storage on client side" which basically says use option 1. – Shane Andrie May 15 '18 at 14:23
  • @Ilya can you please elaborate this "**With Content-Security-Policy enabled, attacked can't steal/send to himself jwt, but still can make request directly from client browser.**" – Shiv Sahni May 16 '18 at 08:02
  • 1
    I just build an identical setup and went with option 1. As you quickly recap, XSS is always a problem, so it's just better to go with the simpler solution and focus your efforts on fixing any XSS hole. – BgrWorker Jun 14 '18 at 13:09

1 Answers1

6

Let's begin with the easy part: Option #2 is a non-options. It comes with no CSRF-protection, so it is inherently vulnerable. Actually, option #3 is pretty much option #2 with CSRF-protection on top. So you are right to focus your question on the choice between #1 and #3.

Are there any other benefits of Option 3 over Option 1?

Not really. And the I'm not sure the benefit you mention - being able to protect the JWT with the Http-Only flag - is that big of a benefit.

Being able to steal the JWT is convenient for the attacker, since she can then do whatever she wants to do from her own computer. But given a XSS vulnerability, anything she could do on her own computer with the JWT she can also do from the victims computer using injected scripts. That might be a bit more annoying for the attacker, but it would not stop a determined adversary.

So in the end, I think the choice between #1 and #3 mostly is one of personal preference. I personally like #1 better, because I find it cleaner and more straight forward. Plus it takes care of the CSRF situation in a neat way, without me having to lift a finger.

If I go with Option 3, am I introducing some other attack vectors, that I havent't considered yet?

Maybe.

Generally, one weakness with the double submit cookie pattern is that it allows for subdomain attacks. This is somewhat mitigated by the use of a JWT. Since it is signed, a sibling domain could not rewrite that cookie. But (at least in some browsers) a sub domain could read it and hence perform a CSRF attack. Using a HTTP header rather than a form field for the double submit would make this attack much harder, if not impossible (barring something like a Flash exploit).

Note that the security of this option completely relies on you getting the CSRF proteciton right.

Is there any other way of CSRF mitigation, instead of keeping state in front-end (localStorage/sessionStorage/Redux store/JS-readable cookie) which is always vulnerable to XSS?

No. If you're vulnerable to XSS, all CSRF protections goes out the window. And it doesn't really matter. Because if I can do XSS, I have no need for CSRF anymore. So trying to come up with a way to protect from CSRF in case of XSS is not only impossible, it's also pointless.

Every minute you spend thinking about how to protect your CSRF token in case of a XSS attack is a minute you should have spent trying to stop XSS attacks instead.

Anders
  • 64,406
  • 24
  • 178
  • 215