Reputation: 26788
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
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
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