Ajax and Request Forgery Protection
Rails 2.1 added protection for cross-site request forgery by embedding a session-based token in generated forms. Rails will not process a POSTed request without the token. For the most part, this protection is transparent. But occasionally, an Ajax request request gets left out in the cold without a token.
If your Ajax request is tied to a form on the page, then all is good because the form already has the authenticity token in it. It only happens when your Ajax request is not tied to a form but makes a POST request, which is a rare but occasionally useful.
So how do we let those Ajax requests in on the fun? We came up with the ingenious idea of just embedding the authenticity token in a meta tag on every page, which can then be used in the Javascript.
<meta name="authenticity-token" id="authenticity-token" content="<%= form_authenticity_token %>" />
The authenticity token is unique for each visitor, and already included in other parts of the page, so this doesn’t defeat the purpose of the request forgery protection.
We usually add a couple convenience methods for accessing the token in Javascript.
var Application = {
authenticityToken: function() {
return $('authenticity-token').content;
},
authenticityTokenParameter: function(){
return 'authenticity_token=' + encodeURIComponent(Application.authenticityToken());
}
}
Now, we have easy access to it whenever we need it.
new Ajax.Request(url, {
parameters: Application.authenticityTokenParameter()
});
2 comments
An even nicer solution is to extend the Base class of the Prototype Ajax class hierarchy as follows. Now you never have to worry about including the authenticity token in your Ajax requests / updates – it’s an included parameter in all Ajax requests!
Ajax.Base.prototype.initialize = Ajax.Base.prototype.initialize.curry( { parameters: Application.authenticityTokenParameter() });Thanks for the code! It is a very elegant solution and has helped me out.
Oops! My initial attempt was a bit optimistic :P Only the token parameter was being passed through. Here’s something that works:
Ajax.Base.prototype.initialize = Ajax.Base.prototype.initialize.wrap(function() { var args = $A(arguments), proceed = args.shift(); var options = args[0]; var token = encodeURIComponent(Application.authenticityToken()); if (Object.isString(options.parameters)) { options.parameters += '&' + token; } else if (Object.isHash(options.parameters)) { options.parameters = this.options.parameters.toObject(); options.parameters.authenticity_token = token; } else if (options.parameters != undefined) { options.parameters.authenticity_token = token; } proceed.apply(null, args); });Speak your mind: