Hot on the heels of the in-depth look at Rails security options comes the addition of the CsrfKiller plugin into rails core.
The CsrfKiller plugin adds a unique session token field to all forms that is checked on every non-GET request. This ensures that the request received is in fact coming from the session of the authorized user (check out wikipedia’s CSRF article if you need more details on the technique).
All you need to do to enable this protection is to add a protect_from_forgery statement in your controller that takes the familiar :except or :only option along with salt to use when generating the unique token:
And now this is the default functionality. No need to specify anything if you want the default behavior. You can still override as in the following examples, however.
1 2 3 4 5 |
class PostsController < ApplicationController protect_from_forgery :secret => '2kdjnaLI8', :only => [:update, :delete, :create] ... end |
If you’re already using edge Rails’ default cookie session store then you don’t have to specify the :secret key.
protect_from_forgery :only => [:update, :delete, :create] |
If you’re not on a cookie session store you can also change the digest method used to generate the unique token (the default is ‘SHA1’).
protect_from_forgery :secret => '2kaienna9ea90djnaLI8', :digest => 'MD5' |
If a request comes in that doesn’t match the request forgery protection token for the current session then an ActionController::InvalidAuthenticityToken exception will be thrown. Perhaps a good place to try out the new exception handling syntax ?
- Non-GET requests, so make sure the only requests that modify state are your
PUT/POST/DELETErequests. - On html and ajax requests. Override
verifiable_request_format?if you want to expand that.
And what to do if you want to disable CSRF protection? Just add this to whatever controllers you don’t want to be affected:
skip_before_filter :verify_authenticity_token |
And if you want to disable this site-wide, just add this to application.rb:
self.allow_forgery_protection = false |
tags: ruby, rubyonrails

One interesting bug with this is if you turn on active_record_store, you’ll be greeted with this error:
undefined method `generate_digest’ for #<cgi::session::activerecordstore:0x303f7fc>
A quick way around it is to add the secret to protect_from_forgery, instead of relying on it in your environment.rb file.
I’m looking at this a bit closer to see what the issue is.
This seems like it would kill REST clients trying to make non-GET requests to resources, no?
No, it only protects based on the format (see #verifiable_request_format? http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/request_forgery_protection.rb#L88). XML/JSON api requests would typically use some other session-less form of authentication, such as http basic or an api token.
Robert: thanks for the heads up. For those playing at home, there’s a rails ticket for it: http://dev.rubyonrails.org/ticket/9670 and it’ll be fixed for the 2.0 preview…
ok try this for the trac link. How sad is it the author of this blog tool can’t post comments correctly with it?
One other thing I noticed, when doing an ajax request (link_to_remote) you need to explicitly state what type of request. (:method => :get) I added this the trac.
As Rick pointed out in the trac ticket, I was mistaken by the above. I wasn’t even thinking that link_to_remote was a POST by default…doh. Thanks for the clarification Rick.
One thing I noticed is that tests for because the mock for cookie cgi doesn’t have a dbman method (unlike the cookie session store)
How do you guys work around this? Just adding a stub method for the cookie cgi? Suggestions would be nice
(I worked around this by using a diff session management store during tests)
Good post – I was just looking for the info on how to skip the forgery protection for one of my controllers. However, why isn’t this info in your rails 2 pdf, which i incidentally have just bought!
Hey Philip,
This isn’t in the peepcode because I wasn’t sure how prevalent its usage would be and we had to draw the line somewhere. However, a few more people making a request such as yours and I’ll be sure to add it!
Thanks for the feedback.
This is really a good point to cover in my presentation today ;-)
Thanks Ryan,
Hi, This seems to be why my ajax requests (using JQuery’s load and url_for) fail when I’m sending additional parameters to the load function. This changes it from a Get to a POST.
Could someone post a way to manually add the authenticate token for this newbie?
TIA steve
I actually added this line
self.allow_forgery_protection = false
to my application controller, but this did NOT change anything, I still get the usual error
InvalidAuthenticityToken
I actually added this line
self.allow_forgery_protection = false
to my application controller, but this did NOT change anything, I still get the usual error
InvalidAuthenticityToken
I discovered that when running a Facebook application on Rails, you should explicitly set your own salt rather than relying on the Cookie Session Store to generate it for you. I haven’t looked deeply into how the cookie generates this salt, but it’s clear that it changes in between requests when round tripping in Facebook, thus making Rails mark it as invalid.
My write up on the issue is here: http://blog.ror.com.my/2007/11/28/facebook-rails-gotchas-csrf-protection-and-cookie-session-store/
Hope this will help someone else when Googling for the same problem.
I was having the same problem as Kamal, except not with Facebook. If validation failed, I would get an ActionController::InvalidAuthenticityToken error when re-submitting the form.
However, the problem wasn’t with Rails, it was with an older version of Restful Authentication, as detailed in this ticket.
To fix it, I just had to remove the
reset_sessioncall from UsersController#create.