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.