A good reason, which you have sort of touched on, is that once the CSRF cookie has been received, it is then available for use throughout the application in client script for use in both regular forms and AJAX POSTs. This will make sense in a JavaScript heavy application such as one employed by AngularJS (using AngularJS doesn't require that the application will be a single page app, so it would be useful where state needs to flow between different page requests where the CSRF value cannot normally persist in the browser).
Consider the following scenarios and processes in a typical application for some pros and cons of each approach you describe. These are based on the Synchronizer Token Pattern.
Request Body Approach
- User successfully logs in.
- Server issues auth cookie.
- User clicks to navigate to a form.
- If not yet generated for this session, server generates CSRF token, stores it against the user session and outputs it to a hidden field.
- User submits form.
- Server checks hidden field matches session stored token.
Advantages:
- Simple to implement.
- Works with AJAX.
- Works with forms.
- Cookie can actually be HTTP Only.
Disadvantages:
- All forms must output the hidden field in HTML.
- Any AJAX POSTs must also include the value.
- The page must know in advance that it requires the CSRF token so it can include it in the page content so all pages must contain the token value somewhere, which could make it time consuming to implement for a large site.
Custom HTTP Header (downstream)
- User successfully logs in.
- Server issues auth cookie.
- User clicks to navigate to a form.
- Page loads in browser, then an AJAX request is made to retrieve the CSRF token.
- Server generates CSRF token (if not already generated for session), stores it against the user session and outputs it to a
header.
- User submits form (token is sent via hidden field).
- Server checks hidden field matches session stored token.
Advantages:
Disadvantages:
- Doesn't work without an AJAX request to get the header value.
- All forms must have the value added to its HTML dynamically.
- Any AJAX POSTs must also include the value.
- The page must make an AJAX request first to get the CSRF token, so it will mean an extra round trip each time.
- Might as well have simply output the token to the page which would save the extra request.
Custom HTTP Header (upstream)
- User successfully logs in.
- Server issues auth cookie.
- User clicks to navigate to a form.
- If not yet generated for this session, server generates CSRF token, stores it against the user session and outputs it in the page content somewhere.
- User submits form via AJAX (token is sent via header).
- Server checks custom header matches session stored token.
Advantages:
Disadvantages:
- Doesn't work with forms.
- All AJAX POSTs must include the header.
Custom HTTP Header (upstream & downstream)
- User successfully logs in.
- Server issues auth cookie.
- User clicks to navigate to a form.
- Page loads in browser, then an AJAX request is made to retrieve the CSRF token.
- Server generates CSRF token (if not already generated for session), stores it against the user session and outputs it to a
header.
- User submits form via AJAX (token is sent via header) .
- Server checks custom header matches session stored token.
Advantages:
Disadvantages:
- Doesn't work with forms.
- All AJAX POSTs must also include the value.
- The page must make an AJAX request first to get the CRSF token, so it will mean an extra round trip each time.
Set-Cookie
- User successfully logs in.
- Server issues auth cookie.
- User clicks to navigate to a form.
- Server generates CSRF token, stores it against the user session and outputs it to a cookie.
- User submits form via AJAX or via HTML form.
- Server checks custom header (or hidden form field) matches session stored token.
- Cookie is available in browser for use in additional AJAX and form requests without additional requests to server to retrieve the CSRF token.
Advantages:
- Simple to implement.
- Works with AJAX.
- Works with forms.
- Doesn't necessarily require an AJAX request to get the cookie value. Any HTTP request can retrieve it and it can be appended to all forms/AJAX requests via JavaScript.
- Once the CSRF token has been retrieved, as it is stored in a cookie the value can be reused without additional requests.
Disadvantages:
- All forms must have the value added to its HTML dynamically.
- Any AJAX POSTs must also include the value.
- The cookie will be submitted for every request (i.e. all GETs for images, CSS, JS, etc, that are not involved in the CSRF process) increasing request size.
- Cookie cannot be HTTP Only.
So the cookie approach is fairly dynamic offering an easy way to retrieve the cookie value (any HTTP request) and to use it (JS can add the value to any form automatically and it can be employed in AJAX requests either as a header or as a form value). Once the CSRF token has been received for the session, there is no need to regenerate it as an attacker employing a CSRF exploit has no method of retrieving this token. If a malicious user tries to read the user's CSRF token in any of the above methods then this will be prevented by the Same Origin Policy. If a malicious user tries to retrieve the CSRF token server side (e.g. via curl
) then this token will not be associated to the same user account as the victim's auth session cookie will be missing from the request (it would be the attacker's - therefore it won't be associated server side with the victim's session).
As well as the Synchronizer Token Pattern there is also the Double Submit Cookie CSRF prevention method, which of course uses cookies to store a type of CSRF token. This is easier to implement as it does not require any server side state for the CSRF token. The CSRF token in fact could be the standard authentication cookie when using this method, and this value is submitted via cookies as usual with the request, but the value is also repeated in either a hidden field or header, of which an attacker cannot replicate as they cannot read the value in the first place. It would be recommended to choose another cookie however, other than the authentication cookie so that the authentication cookie can be secured by being marked HttpOnly. So this is another common reason why you'd find CSRF prevention using a cookie based method.
Choosing the storage is more about trade-offs than trying to find a definitive best choice. Let's go through a few options:
Option 1 - Web Storage (localStorage
or sessionStorage
)
Pros
- The browser will not automatically include anything from Web storage into HTTP requests making it not vulnerable to CSRF
- Can only be accessed by Javascript running in the exact same domain that created the data
- Allows to use the most semantically correct approach to pass token authentication credentials in HTTP (the
Authorization
header with a Bearer
scheme)
- It's very easy to cherry pick the requests that should contain authentication
Cons
- Cannot be accessed by Javascript running in a sub-domain of the one that created the data (a value written by
example.com
cannot be read by sub.example.com
)
- ⚠️ Is vulnerable to XSS
- In order to perform authenticated requests you can only use browser/library API's that allow for you to customize the request (pass the token in the
Authorization
header)
Usage
You leverage the browser localStorage
or sessionStorage
API to store and then retrieve the token when performing requests.
localStorage.setItem('token', 'asY-x34SfYPk'); // write
console.log(localStorage.getItem('token')); // read
Option 2 - HTTP-only cookie
Pros
- It's not vulnerable to XSS
- The browser automatically includes the token in any request that meets the cookie specification (domain, path and lifetime)
- The cookie can be created at a top-level domain and used in requests performed by sub-domains
Cons
- ⚠️ It's vulnerable to CSRF
- You need to be aware and always consider the possible usage of the cookies in sub-domains
- Cherry picking the requests that should include the cookie is doable but messier
- You may (still) hit some issues with small differences in how browsers deal with cookies
- ⚠️ If you're not careful you may implement a CSRF mitigation strategy that is vulnerable to XSS
- The server-side needs to validate a cookie for authentication instead of the more appropriate
Authorization
header
Usage
You don't need to do anything client-side as the browser will automatically take care of things for you.
Option 3 - Javascript accessible cookie ignored by server-side
Pros
- It's not vulnerable to CSRF (because it's ignored by the server)
- The cookie can be created at a top-level domain and used in requests performed by sub-domains
- Allows to use the most semantically correct approach to pass token authentication credentials in HTTP (the
Authorization
header with a Bearer
scheme)
- It's somewhat easy to cherry pick the requests that should contain authentication
Cons
- ⚠️ It's vulnerable to XSS
- If you're not careful with the path where you set the cookie then the cookie is included automatically by the browser in requests which will add unnecessary overhead
- In order to perform authenticated requests you can only use browser/library API's that allow for you to customize the request (pass the token in the
Authorization
header)
Usage
You leverage the browser document.cookie
API to store and then retrieve the token when performing requests. This API is not as fine-grained as the Web storage (you get all the cookies) so you need extra work to parse the information you need.
document.cookie = "token=asY-x34SfYPk"; // write
console.log(document.cookie); // read
Additional Notes
This may seem a weird option, but it does has the nice benefit that you can have storage available to a top-level domain and all sub-domains which is something Web storage won't give you. However, it's more complex to implement.
Conclusion - Final Notes
My recommendation for most common scenarios would be to go with Option 1, mostly because:
- If you create a Web application you need to deal with XSS; always, independently of where you store your tokens
- If you don't use cookie-based authentication CSRF should not even pop up on your radar so it's one less thing to worry about
Also note that the cookie based options are also quite different, for Option 3 cookies are used purely as a storage mechanism so it's almost as if it was an implementation detail of the client-side. However, Option 2 means a more traditional way of dealing with authentication; for a further read on this cookies vs token thing you may find this article interesting: Cookies vs Tokens: The Definitive Guide.
Finally, none of the options mention it, but use of HTTPS is mandatory of course, which would mean cookies should be created appropriately to take that in consideration.
Best Solution
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: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 theX-XSRF-TOKEN
header and compare it with theXSRF-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: