Forgery Protection Strategy

May 06, 2013 by Alex Coco

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.