MooTools and Rails CSRF Protection

Wow! This takes me back! Please check the date this post was authored, as it may no longer be relevant in a modern context.

“Cross site request forgery” is also known as CSRF, XSRF or just request forgery (more at wikipedia and sans.org). It’s a method of attack toward web applications- Rails 2.0 introduced a defence and Rails 2.1 enabled that defence by default. Call form_for…

<% form_for @friend, :action => 'create' do |f| %>

and Rails spits out more than just the form, it also generates a secret authenticity_token:

<form action="/friends" method="post">
<div style="margin:0;padding:0">
  <input name="authenticity_token" type="hidden" value="e8c827c47577e013cc4c06a99cab63da95b71915" />
</div>

AJAX submission of this particular form will include the authenticity token. So here’s the rub: what if the form is generated by something else?

new Element('form', { 'action': '/friends'});

Or what if there is no form?!

new Request.Json( ... ).post();

In these cases Rails would raise an ActionController::InvalidAuthenticityToken exception. To avoid an exception the CSRF check can be altogether disabled for a controller:

skip_before_filter :verify_authenticity_token

Of course this is a compromise between convenience and security. For those who want security and AJAXy goodness (well, with MooTools at least), there is a better way.

Using AuthenticityToken with MooTools

MooTools has a wonderful object oriented codebase. First Rails needs to pass the authenticity_token into Javascript, then we can use the inheritance from MooTools to pass the authenticity_token with every request.

The authenticity_token can be passed in the header of the Rails application layout. It’s just one line:

<%= javascript_tag "const AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? %>

And now pass that token with every POST request (GET requests don’t check CSRF):

// Include authenticity_token in Request.JSON fires
//
// move the send function
Request.prototype._send = Request.prototype.send
Request.implement({

  auth_token: function(){
    return AUTH_TOKEN;
  },

  // This is more verbose than is ideal, but I don't see a better place
  // to hook this functionality in
  send: function(options){
    var type = $type(options);
    if (type == 'string' || type == 'element') options = {data: options};

    var old = this.options;
    options = $extend({data: old.data, url: old.url, method: old.method}, options);

    switch ($type(options.data)){
      case 'element': options.data = $(options.data).toQueryString(); break;
      case 'object': case 'hash': options.data = Hash.toQueryString(options.data);
    }

    // If this isn't a get request add the authenticity_token
    if (options.method != 'get' || options.method != 'GET')
      options.data = options.data+'&authenticity_token='+this.auth_token();

    // Call the original send
    this._send(options)
  }

});

Look at that, now every AJAX call from MooTools will attach our CSRF. All the ease of normal javascript, all the security of the native Rails defences.