April 6, 2016
This blog is part of our Rails 5 series.
What makes Rails a great framework to work with is its sane
conventions over configuration.
Rails community is always striving to keep these conventions relevant over time.
In this blog, we will see why and what changed in execution order of
protect_from_forgery
.
protect_from_forgery
protects applications against CSRF.
Follow that link to read up more about CSRF
.
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
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.
Let's take an example to understand how this change might affect the upgrade of applications from Rails 4 to Rails 5.
class ApplicationController < ActionController::Base
before_action :set_access_time
protect_from_forgery
private
def set_access_time
current_user.access_time = Time.now
current_user.save
end
end
In Rails 4.x, set_access_time
is not executed for bad requests. But it
gets executed in Rails 5 because protect_from_forgery
is called after
set_access_time
.
Saving data (current_user.save
) in before_action
is anyways a big enough
violation of the best practices, but now those persistences would leave us
vulnerable to CSRF if they are called before protect_from_forgery
is called.
If this blog was helpful, check out our full blog archive.