Cross-Site Forgery Protection in Rails Tests

Cross-Site Forgery Protection (also known as CSRF Protection) is a built-in feature in Rails.

Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks by including a token in the rendered HTML for your application. This token is stored as a random string in the session, to which an attacker does not have access. When a request reaches your application, Rails verifies the received token with the token in the session. All requests are checked except GET requests as these should be idempotent. Keep in mind that all session-oriented requests should be CSRF protected, including JavaScript and HTML requests.

However, by default this protection is switched off in test environments. This makes it easy to test controllers without having to provide a CSRF token with every request, but it can cause problems since our tests are not completely representative of real-world requests.

This is especially true for API controllers, where sessions are not used and CSRF tokens are therefore not used. This means API controllers would ususually skip the CSRF token check by doing skip_before_action :verify_authenticity_token. However, if you forget to skip this check your tests will still pass since forgery protection is completely switched off! In order to avoid this problem I tried to see if it’s possible to configure RSpec to enable forgery protection for API tests.

# spec/rails_helper.rb
RSpec.configure do |config|

  config.define_derived_metadata(file_path: Regexp.new('/spec/requests/api/')) do |metadata|
    metadata[:allow_forgery_protection] = true
  end

  config.around(:each, allow_forgery_protection: true) do |example|
    original_forgery_protection = ActionController::Base.allow_forgery_protection
    ActionController::Base.allow_forgery_protection = true
    begin
      example.run
    ensure
      ActionController::Base.allow_forgery_protection = original_forgery_protection
    end
  end

end

Using this configuration your tests will perform the normal CSRF checks, which means you will get a 422 response unless your controllers skip the authenticity token checks. At the same time the rest of your test suite will run without forgery protection, which may or may not be the desired outcome - but it matches the existing behavior.