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

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.