Should JWT be stored in localStorage or cookie?
A timely post from Stormpath has pretty much elaborated my points and answered my question.
TL;DR
Store the JWT in cookies, then either pass the JWT in the Authorization header on every request like I've mentioned, or as the article suggests, rely on the backend to prevent CSRF (e.g. using xsrfToken
in case of Angular).
I like the XSRF Double Submit Cookies method which mentioned in the article that @pkid169 said, but there is one thing that article doesn't tell you. You are still not protected against XSS because what the attacker can do is inject script that reads your CSRF cookie (which is not HttpOnly) and then make a request to one of your API endpoints using this CSRF token with JWT cookie being sent automatically.
So in reality you are still susceptible to XSS, it's just that attacker can't steal you JWT token for later use, but he can still make requests on your users behalf using XSS.
Whether you store your JWT in a localStorage or you store your XSRF-token in not http-only cookie, both can be grabbed easily by XSS. Even your JWT in HttpOnly cookie can be grabbed by an advanced XSS attack.
So in addition of the Double Submit Cookies method, you must always follow best practices against XSS including escaping contents. This means removing any executable code that would cause the browser to do something you don’t want it to. Typically this means removing // <![CDATA[ tags and HTML attributes that cause JavaScript to be evaluated.
- Do not store your token in LocalStorage or SessionStorage, because such token can be read from javascript and therefore it is vulnarable to XSS attack.
- Do not store your token in Cookie. Cookie (with HttpOnly flag) is a better option - it's XSS prone, but it's vulnarable to CSRF attack
Instead, on login, you can deliver two tokens: access token and refresh token. Access token should be stored in Javascript memory and Refresh token should be stored in HttpOnly Cookie. Refresh token is used only and only for creating new access tokens - nothing more.
When user opens new tab, or on site refresh, you need to perform request to create new access token, based on refresh token which is stored in Cookie.
I also strongly recommend to read this article: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/