Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
This is turned on with the protect_from_forgery
method, which
will check the token and raise an
ActionController::InvalidAuthenticityToken if it doesn't match what was
expected. You can customize the error message in production by editing
public/422.html. A call to this method in ApplicationController is
generated by default in post-Rails 2.0 applications.
The token parameter is named authenticity_token
by default. If
you are generating an HTML form manually
(without the use of Rails' form_for
, form_tag
or
other helpers), you have to include a hidden field named like that and set
its value to what is returned by form_authenticity_token
.
Request forgery protection is disabled by default in test environment. If you are upgrading from Rails 1.x, add this to config/environments/test.rb:
# Disable request forgery protection in test environment config.action_controller.allow_forgery_protection = false
Here are some resources:
Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application. There are a few guidelines you should follow:
Keep your GET requests safe and idempotent. More reading material:
Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session"
The form's authenticity parameter. Override to provide your own.
# File lib/action_controller/metal/request_forgery_protection.rb, line 118 def form_authenticity_param params[request_forgery_protection_token] end
Sets the token value for the current session.
# File lib/action_controller/metal/request_forgery_protection.rb, line 113 def form_authenticity_token session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32) end
# File lib/action_controller/metal/request_forgery_protection.rb, line 97 def handle_unverified_request reset_session end
# File lib/action_controller/metal/request_forgery_protection.rb, line 122 def protect_against_forgery? allow_forgery_protection end
Returns true or false if a request is verified. Checks:
is it a GET request? Gets should be safe and idempotent
Does the #form_authenticity_token match the given token value from the params?
Does the X-CSRF-Token header match the #form_authenticity_token
# File lib/action_controller/metal/request_forgery_protection.rb, line 106 def verified_request? !protect_against_forgery? || request.get? || form_authenticity_token == params[request_forgery_protection_token] || form_authenticity_token == request.headers['X-CSRF-Token'] end
The actual before_filter that is used. Modify this to change how you handle unverified requests.
# File lib/action_controller/metal/request_forgery_protection.rb, line 93 def verify_authenticity_token verified_request? || handle_unverified_request end