Cross Site Request Forgery in JS Web Apps

Ensuring that attackers don't forge requests in your web applications can be a tricky businesses, one that often requires a hand-rolled solution.

As soon as you have a session, you need to start thinking about cross site request forgery (CSRF). Every request to your site will contain authentication cookies, and HTML forms don't abide by the same origin policy (SOP).

One method of ensuring that destructive requests (PUTs/POSTs/DELETEs) to your site are made from your domain, is by only allowing requests with a Content-Type header of application/json. The only way to set this header is via Ajax, and Ajax requests are limited to the same domain.

However, there have been active vectors in the past that have allowed header injection (such as some of the Flash exploits), and Egor, who is the expert in these things, assures me it's not enough.

The classic method of preventing CSRF attacks is via a token that you pass with every destructive request. The idea is that attackers can't get hold of this token (because of the SOP), and thus can't forge requests.

If you're using Rails, you get this for free. If you're using Rack, than Rack CSRF is your best bet. It'll deal with generating tokens and checking requests. The only part you have to handle is on the client side.

jQuery has a neat feature called ajaxPrefilter, which lets you provide a callback to be invoked every Ajax request. You can pass a CSRF token to the client side via, say, a meta tag. Then set a header using ajaxPrefilter.

var CSRF_HEADER = 'X-CSRF-Token';

var setCSRFToken = function(securityToken) {
  jQuery.ajaxPrefilter(function(options, _, xhr) {
    if ( !xhr.crossDomain ) 
        xhr.setRequestHeader(CSRF_HEADER, securityToken);
  });
};

setCSRFToken($('meta[name="csrf-token"]').attr('content'));

In the example above we're retrieving our CSRF token from a meta tag in the page. Then we're ensuring that any local Ajax requests forward the token as part of the request's header.