[security] Why is it common to put CSRF prevention tokens in cookies?

I'm trying to understand the whole issue with CSRF and appropriate ways to prevent it. (Resources I've read, understand, and agree with: OWASP CSRF Prevention CHeat Sheet, Questions about CSRF.)

As I understand it, the vulnerability around CSRF is introduced by the assumption that (from the webserver's point of view) a valid session cookie in an incoming HTTP request reflects the wishes of an authenticated user. But all cookies for the origin domain are magically attached to the request by the browser, so really all the server can infer from the presence of a valid session cookie in a request is that the request comes from a browser which has an authenticated session; it cannot further assume anything about the code running in that browser, or whether it really reflects user wishes. The way to prevent this is to include additional authentication information (the "CSRF token") in the request, carried by some means other than the browser's automatic cookie handling. Loosely speaking, then, the session cookie authenticates the user/browser and the CSRF token authenticates the code running in the browser.

So in a nutshell, if you're using a session cookie to authenticate users of your web application, you should also add a CSRF token to each response, and require a matching CSRF token in each (mutating) request. The CSRF token then makes a roundtrip from server to browser back to server, proving to the server that the page making the request is approved by (generated by, even) that server.

On to my question, which is about the specific transport method used for that CSRF token on that roundtrip.

It seems common (e.g. in AngularJS, Django, Rails) to send the CSRF token from server to client as a cookie (i.e. in a Set-Cookie header), and then have Javascript in the client scrape it out of the cookie and attach it as a separate XSRF-TOKEN header to send back to the server.

(An alternate method is the one recommended by e.g. Express, where the CSRF token generated by the server is included in the response body via server-side template expansion, attached directly to the code/markup that will supply it back to the server, e.g. as a hidden form input. That example is a more web 1.0-ish way of doing things, but would generalize fine to a more JS-heavy client.)

Why is it so common to use Set-Cookie as the downstream transport for the CSRF token / why is this a good idea? I imagine the authors of all these frameworks considered their options carefully and didn't get this wrong. But at first glance, using cookies to work around what's essentially a design limitation on cookies seems daft. In fact, if you used cookies as the roundtrip transport (Set-Cookie: header downstream for the server to tell the browser the CSRF token, and Cookie: header upstream for the browser to return it to the server) you would reintroduce the vulnerability you are trying to fix.

I realize that the frameworks above don't use cookies for the whole roundtrip for the CSRF token; they use Set-Cookie downstream, then something else (e.g. a X-CSRF-Token header) upstream, and this does close off the vulnerability. But even using Set-Cookie as the downstream transport is potentially misleading and dangerous; the browser will now attach the CSRF token to every request including genuine malicious XSRF requests; at best that makes the request bigger than it needs to be and at worst some well-meaning but misguided piece of server code might actually try to use it, which would be really bad. And further, since the actual intended recipient of the CSRF token is client-side Javascript, that means this cookie can't be protected with http-only. So sending the CSRF token downstream in a Set-Cookie header seems pretty suboptimal to me.

This question is related to security cookies web csrf owasp

The answer is


Using a cookie to provide the CSRF token to the client does not allow a successful attack because the attacker cannot read the value of the cookie and therefore cannot put it where the server-side CSRF validation requires it to be.

The attacker will be able to cause a request to the server with both the auth token cookie and the CSRF cookie in the request headers. But the server is not looking for the CSRF token as a cookie in the request headers, it's looking in the payload of the request. And even if the attacker knows where to put the CSRF token in the payload, they would have to read its value to put it there. But the browser's cross-origin policy prevents reading any cookie value from the target website.

The same logic does not apply to the auth token cookie, because the server is expects it in the request headers and the attacker does not have to do anything special to put it there.


My best guess as to the answer: Consider these 3 options for how to get the CSRF token down from the server to the browser.

  1. In the request body (not an HTTP header).
  2. In a custom HTTP header, not Set-Cookie.
  3. As a cookie, in a Set-Cookie header.

I think the 1st one, request body (while demonstrated by the Express tutorial I linked in the question), is not as portable to a wide variety of situations; not everyone is generating every HTTP response dynamically; where you end up needing to put the token in the generated response might vary widely (in a hidden form input; in a fragment of JS code or a variable accessible by other JS code; maybe even in a URL though that seems generally a bad place to put CSRF tokens). So while workable with some customization, #1 is a hard place to do a one-size-fits-all approach.

The second one, custom header, is attractive but doesn't actually work, because while JS can get the headers for an XHR it invoked, it can't get the headers for the page it loaded from.

That leaves the third one, a cookie carried by a Set-Cookie header, as an approach that is easy to use in all situations (anyone's server will be able to set per-request cookie headers, and it doesn't matter what kind of data is in the request body). So despite its downsides, it was the easiest method for frameworks to implement widely.


Besides the session cookie (which is kind of standard), I don't want to use extra cookies.

I found a solution which works for me when building a Single Page Web Application (SPA), with many AJAX requests. Note: I am using server side Java and client side JQuery, but no magic things so I think this principle can be implemented in all popular programming languages.

My solution without extra cookies is simple:

Client Side

Store the CSRF token which is returned by the server after a succesful login in a global variable (if you want to use web storage instead of a global thats fine of course). Instruct JQuery to supply a X-CSRF-TOKEN header in each AJAX call.

The main "index" page contains this JavaScript snippet:

// Intialize global variable CSRF_TOKEN to empty sting. 
// This variable is set after a succesful login
window.CSRF_TOKEN = '';

// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
    jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
}); 

Server Side

On successul login, create a random (and long enough) CSRF token, store this in the server side session and return it to the client. Filter certain (sensitive) incoming requests by comparing the X-CSRF-TOKEN header value to the value stored in the session: these should match.

Sensitive AJAX calls (POST form-data and GET JSON-data), and the server side filter catching them, are under a /dataservice/* path. Login requests must not hit the filter, so these are on another path. Requests for HTML, CSS, JS and image resources are also not on the /dataservice/* path, thus not filtered. These contain nothing secret and can do no harm, so this is fine.

@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
    resp.sendError(401);
} else
    chain.doFilter(request, response);
}   

Examples related to security

Monitoring the Full Disclosure mailinglist Two Page Login with Spring Security 3.2.x How to prevent a browser from storing passwords JWT authentication for ASP.NET Web API How to use a client certificate to authenticate and authorize in a Web API Disable-web-security in Chrome 48+ When you use 'badidea' or 'thisisunsafe' to bypass a Chrome certificate/HSTS error, does it only apply for the current site? How does Content Security Policy (CSP) work? How to prevent Screen Capture in Android Default SecurityProtocol in .NET 4.5

Examples related to cookies

SameSite warning Chrome 77 How to fix "set SameSite cookie to none" warning? Set cookies for cross origin requests Make Axios send cookies in its requests automatically How can I set a cookie in react? Fetch API with Cookie How to use cookies in Python Requests How to set cookies in laravel 5 independently inside controller Where does Chrome store cookies? Sending cookies with postman

Examples related to web

Cannot open local file - Chrome: Not allowed to load local resource How do I detect if a user is already logged in Firebase? HTML button opening link in new tab What does "app.run(host='0.0.0.0') " mean in Flask Web link to specific whatsapp contact App not setup: This app is still in development mode How to delete/remove nodes on Firebase Cannot read property 'push' of undefined when combining arrays HTML: Image won't display? JWT (JSON Web Token) library for Java

Examples related to csrf

Post request in Laravel - Error - 419 Sorry, your session/ 419 your page has expired "The page has expired due to inactivity" - Laravel 5.5 Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN' Why is it common to put CSRF prevention tokens in cookies? include antiforgerytoken in ajax post ASP.NET MVC Cross Domain Form POSTing WARNING: Can't verify CSRF token authenticity rails How to properly add cross-site request forgery (CSRF) token using PHP What is a CSRF token? What is its importance and how does it work? Django CSRF check failing with an Ajax POST request

Examples related to owasp

Why is it common to put CSRF prevention tokens in cookies? What is "X-Content-Type-Options=nosniff"? PHP $_SERVER['HTTP_HOST'] vs. $_SERVER['SERVER_NAME'], am I understanding the man pages correctly?