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.