Forgery Protection Strategy
I was pretty puzzled when I started a new Rails 4 app a week ago. I wanted to have a controller authenticate with Devise’s token authentication and respond to JSON requests. I kept running into an ActionController::InvalidAuthenticityToken
exception.
Having already done something similar in Rails 3, I guessed that it had something to do with the move to Rails 4. After searching for anything that had to do with the Authenticity Token and Rails 4 I came up empty. At this point I decided to dive into the Rails source to see what was actually going on when that exception was raised.
It turns out that there was a change made to the way missing authenticity tokens are handled in Rails 4. In a Rails 3 app, you’ll notice that in the ApplicationController
that gets generated along with the application you have a line that says protect_from_forgery
. Looking at the source for that method, you’ll see how Rails will protect your application from requests that are missing Authenticity Tokens.
def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
prepend_before_filter :verify_authenticity_token, options
end
This method will ensure that verify_authenticity_token
gets called before each action. If we take a look at this method now we see that the code that actually handles unverified requests (not surprisingly) is found in the handle_unverified_request
method.
def handle_unverified_request
reset_session
end
handle_unverified_request
just resets the session when it’s called. That’s why there’s no exception when using Rails 3. Let’s take a look at the same method in the Rails 4 source. This time the method is a little different.
def handle_unverified_request
forgery_protection_strategy.new(self).handle_unverified_request
end
What’s this forgery_protection_strategy
thing? Let’s back up a little bit. When generating a Rails 4 application, the ApplicationController
now passes a parameter to protect_from_forgery
.
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
Rails 4 lets you choose between three forgery protection strategies. When the application is generated, the default strategy is set to :exception
which corresponds to ProtectionMethods::Exception
.
class Exception
def initialize(controller)
@controller = controller
end
def handle_unverified_request
raise ActionController::InvalidAuthenticityToken
end
end
When calling protect_from_forgery with: :exception
, Rails will set forgery_protection_strategy
to the appropriate strategy. Then, when an unverified request is made, the handle_unverified_request
method will delegate the responsibility to the appropriate strategy.
In the Rails 4 ApplicationController
there’s a comment that tells you to use the :null_session
strategy if you’re building an API. This strategy empties the session instead of raising an exception which is perfect for an API.