“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…

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

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

1
2
3
4
<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?

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

Or what if there is no form?!

1
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:

1
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:

1
<%= 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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 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.

Thanks for reading and come again, -Matt

6 Responses

  1. atom at June 26th, 2008 at 04:37 AM

    this is some clever stuff, well done.

  2. ashchan at June 26th, 2008 at 09:50 PM

    That’s awesome! Thanks for sharing this tips.

  3. Henrik N at July 25th, 2008 at 08:18 AM

    Nice.

    Note that for my jQuery equivalent, someone commented that IE had issues with the const keyword, so I changed it into var in the post. Haven’t tried in IE myself.

  4. Matthew Beale at July 25th, 2008 at 08:23 AM

    I’ll check that out Henrik, thanks for the heads up!

  5. Jonathan at August 6th, 2008 at 10:32 AM

    You definitely have to change it from const to var for IE 6. Which I wish would just die already.

  6. Alexey at August 6th, 2008 at 12:33 PM

    Hello.

    Can you please help me with moving rails 2.1 to mootools?

    The plugin mootools-on-rails doesn’t work here.

Sorry, comments are closed for this article.