max pleaner
max pleaner

Reputation: 26788

Shouldn't protect_from_forgery with: :exception raise an error if I don't include an authenticity token?

It's my understanding that protect_from_forgery with: :exception which is the default in Rails, will cause an error if forms are submitted and they don't have the authenticity_token input.

However it seems like this is not the case anymore. This is a Rails 5 app, and I've mostly done Rails 4 in the past, so I wonder if something has changed.

In application_controller.rb I have protect_from_forgery with: :exception

My form is like this (using slim)

form#spreadsheet-form{
  action='/submit_spreadsheet'
}
  textarea.spreadsheet-input{
    name='instructions'
    style="width: 200px; height: 200px"
  }
  br
  input.spreadsheet-submit{
    type="submit"
    value="submit"
  }

The main issue in my eyes is why this doesn't raise an error. In the past I've had to include a hidden input with form_authencicity_token as the value.

Upvotes: 1

Views: 964

Answers (2)

Pavan
Pavan

Reputation: 33552

I believe it is something to do with the Rails 5 changed the protect_from_forgery execution order. From this blog

What

If we generate a brand new Rails application in Rails 4.x then application_controller will look like this.

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
end

Looking it at the code it does not look like protect_from_forgery is a before_action call but in reality that’s what it is. Since protect_from_forgery is a before_action call it should follow the order of how other before_action are executed. But this one is special in the sense that protect_from_forgery is executed first in the series of before_action no matter where protect_from_forgery is mentioned. Let’s see an example.

class ApplicationController < ActionController::Base
  before_action :load_user
  protect_from_forgery with: :exception
end

In the above case even though protect_from_forgery call is made after load_user, the protection execution happens first. And we can’t do anything about it. We can’t pass any option to stop Rails from doing this.

Rails 5 changes this behavior by introducing a boolean option called prepend. Default value of this option is false. What it means is, now protect_from_forgery gets executed in order of call. Of course, this can be overridden by passing prepend: true as shown below and now protection call will happen first just like Rails 4.x.

class ApplicationController < ActionController::Base
  before_action :load_user
  protect_from_forgery with: :exception, prepend: true
end

Why

There isn’t any real advantage in forcing protect_from_forgery to be the first filter in the chain of filters to be executed. On the flip side, there are cases where output of other before_action should decide the execution of protect_from_forgery. Let’s see an example.

class ApplicationController < ActionController::Base
  before_action :authenticate
  protect_from_forgery unless: -> { @authenticated_by.oauth? }

  private
    def authenticate
      if oauth_request?
        # authenticate with oauth
        @authenticated_by = 'oauth'.inquiry
      else
        # authenticate with cookies
        @authenticated_by = 'cookie'.inquiry
      end
    end
end

Above code would fail in Rails 4.x, as protect_from_forgery, though called after :authenticate, actually gets executed before it. Due to which we would not have @authenticated_by set properly.

Whereas in Rails 5, protect_from_forgery gets executed after :authenticate and gets skipped if authentication is oauth

Upvotes: 1

Ron Silson
Ron Silson

Reputation: 82

Is the authenticity token present on your form when you inspect it? Which point is protect_from_forgery inserted in your application?

Rails 4=>5 changed the default behaviour to be inserted wherever in the chain it is called, opposed to the previous where it was called first. If you want it to be called before any other action, try using the prepend: true flag.

Upvotes: 0

Related Questions