Where to store JWT in browser? How to protect against CSRF?
I know cookie-based authentication. SSL and HttpOnly flags can be applied to protect cookie-based authentication from MITM and XSS. However, more special measures will be needed to apply in order to protect it from CSRF. They are just a bit complicated. (reference)
Recently, I discover that JSON Web Token (JWT) is quite hot as a solution for authentication. I know the stuff about encoding, decoding, and verifying JWT. However, I don't understand why some websites/tutorials tell that there is no need for CSRF protection if JWT is used. I have read quite a lot and have tried to summarize the problems below. I just want someone to provide a bigger picture of JWT and clarify the concepts I misunderstood about JWT.
-
If the JWT is stored in a cookie, I think it is the same as cookie-based authentication except that the server does not need to have sessions to verify the cookie/token. There is still a risk of CSRF if no special measure is implemented. Isn't JWT stored in a cookie?
-
If the JWT is stored in localStorage/sessionStorage, then there is no cookie involved so don't need to protect against CSRF. The question is how to send the JWT to the server. I found here that it is suggested to use jQuery to send the JWT by HTTP header of ajax requests. So, only the ajax requests can do the authentication?
-
Also, I found one more blog that points to use "Authorization header" and "Bearer" to send the JWT. I don't understand the method the blog talks about. Could someone please explain more about "Authorization header" and "Bearer"? Does this make the JWT transmitted by HTTP header of ALL requests? If yes, what about CSRF?
Solution 1:
We need to store the JWT on the client computer. If we store it in a LocalStorage/SessionStorage then it can be easily grabbed by an XSS attack. If we store it in cookies then a hacker can use it (without reading it) in a CSRF attack and impersonate the user and contact our API and send requests to do actions or get information on behalf of a user.
But there are several ways to secure the JWT in cookies to not to be stolen easily (but there are still some advanced techniques to steal them). But if you wanna rely on LocalStorage/SessionStorage, then it can be accessed by a simple XSS attack.
So to solve the CSRF problem, I use Double Submit Cookies in my application.
Double Submit Cookies Method
Store JWT in a HttpOnly cookie and used it in secure mode to transfer over HTTPS.
Most of CSRF attacks have a different origin or referrer header with your original host in their requests. So check if you have any of them in the header, are they coming from your domain or not! If not reject them. If both origin and referrer are not available in the request then no worries. You can rely on the result of X-XSRF-TOKEN header validation results which I explain in the next step.
While the browser will automatically supply your cookies for the domain of the request, there is one useful limitation: the JavaScript code that is running on a website cannot read the cookies of other websites. We can leverage this to create our CSRF solution. To prevent CSRF attacks, we must create an extra Javascript readable cookie which is called: XSRF-TOKEN. This cookie must be created when the user is logged in and should contain a random, un-guessable string. We also save this number in the JWT itself as a private claim. Every time the JavaScript application wants to make a request, it will need to read this token and send it along in a custom HTTP header. Because these operations (reading the cookie, setting the header) can only be done on the same domain of the JavaScript application, we can know that this is being done by a real user who is using our JavaScript application.
Angular JS makes your life easy
Fortunately, I am using Angular JS in our platform and Angular packages the CSRF token approach, making it simpler for us to implement. For every request that our Angular application makes of the server, the Angular $http
service will do these things automatically:
- Look for a cookie named XSRF-TOKEN on the current domain.
- If that cookie is found, it reads the value and adds it to the request as the X-XSRF-TOKEN header.
Thus the client-side implementation is handled for you, automatically! We just need to set a cookie named XSRF-TOKEN
on the current domain in server side and when our API got any call from the client, it must check the X-XSRF-TOKEN
header and compare it with the XSRF-TOKEN
in the JWT. If they match, then the user is real. Otherwise, it's a forged request and you can ignore it. This method is inspired by the "Double Submit Cookie" method.
Caution
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 the localStorage
or you store your XSRF-token in not HttpOnly cookie, both can be grabbed easily by XSS. Even your JWT in an HttpOnly cookie can be grabbed by an advanced XSS attack like XST method.
So in addition to 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.
Read more here:
- Angular’s XSRF: How It Works
- Where to Store your JWTs – Cookies vs HTML5 Web Storage
Solution 2:
JWT tokens are popular since they are used as the default token format in new authorization and authentication protocols like OAuth 2.0 and OpenID Connect.
When the token is stored in a cookie, the browser will automatically send it along with each request to the same domain and this is still vulnerable to CSRF attacks.
Bearer authentication is one of the authentication schemes defined in HTTP. It basically means that YOU stick the (JWT) token in the Authorization HTTP header of a request. The browser will NOT do this for you automatically, so it's not suitable for protecting your website. As the browser does not automatically add the header to your request, it is not vulnerable to a CSRF attack, which depends on your authentication info being submitted automatically to the original domain.
The bearer scheme is often used to protect web APIs (REST services) that are consumed via AJAX calls or from mobile clients.
Solution 3:
Now in 2020, simply store the JWT token in a cookie with SameSite=strict
to defeat CSRF. Of course, keep secure
and httpOnly
too.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
Solution 4:
Another angle to the whole issue of storing JWTs:
- JWTs should never be stored in your localStorage
- In fact, they shouldn't even be stored in your cookies, unless you are able to implement very strict CSRF protection
Checkout this for motivation
- JWT as an id_token is like your user credentials
- JWT as an access_token is like your session token
The most secure option is in-memory. Checkout this for a deep dive