20

I'm simulating a request coming from an external service, which will not have an authenticity token. I want the test to fail if skip_before_action :verify_authenticity_token is missing.

How do I do this from an Rspec request spec?

Currently I'm using post as below, but it is happily accepted.

post endpoint, my_json_string, {'CONTENT_TYPE' => "application/json"}
James EJ
  • 1,955
  • 1
  • 18
  • 16

3 Answers3

60

CSRF protection disabled in test environment. Try to enable it use:

before do
  ActionController::Base.allow_forgery_protection = true
end

after do
  ActionController::Base.allow_forgery_protection = false
end
jwadsack
  • 5,708
  • 2
  • 40
  • 50
Maxim
  • 9,701
  • 5
  • 60
  • 108
  • 1
    As of Rails 4.2.5.1 and Rspec 3.4, this technique works fine. I suspect this is the proper way to implement the solution. – Martin Streicher Feb 23 '16 at 17:54
  • 1
    For tests of CSRF protection, one may need [the `raise_error` matcher](https://www.relishapp.com/rspec/rspec-expectations/v/3-5/docs/built-in-matchers/raise-error-matcher): `expect { post endpoint, my_json_string }.to raise_error(ActionController::InvalidAuthenticityToken)`. I thought I was supposed to check the return code of response. – Franklin Yu Aug 27 '16 at 22:01
  • Nice solution even for Rails 5 (API) nowadays. – André C. Rocha Jun 17 '21 at 01:16
  • I think, that for `request` specs this should be enabled by default, so I put in `spec_helper.rb` this: ` config.before(:each, type: :request) do ActionController::Base.allow_forgery_protection = true end` and tag correctly request specs – Foton Jan 11 '22 at 09:34
2

Disabling CSRF protection didn't work for me. So I created this module which uses the response body to retrieve the authenticity token:

https://gist.github.com/Rodrigora/440220a2e24bd42b7b0c

Then, I can test put/post requests without disabling forgery protection:

before do
  @token = login(create(:user, password: 'password'))
end

it 'tests model creation' do
   expect {
     post_with_token 'path/to/model', model_params, @token
   }.to change(Model, :count).by(1)
end
Rodrigo
  • 5,435
  • 5
  • 42
  • 78
2

Trying to change ActionController::Base.allow_forgery_protection in a before or a config.around(:each didn't work for me in Rails 6, because the controller already has cached the value of allow_forgery_protection before the rspec callback runs.

The solution which worked was:

allow_any_instance_of(ActionController::Base).to receive(:protect_against_forgery?).and_return(true)

which can be found here.

tommyh
  • 121
  • 1
  • 4