aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/metal/request_forgery_protection.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller/metal/request_forgery_protection.rb')
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb53
1 files changed, 29 insertions, 24 deletions
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 367b736035..64f6f7cf51 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -13,9 +13,14 @@ module ActionController #:nodoc:
# 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. Only HTML and JavaScript requests are checked,
- # so this will not protect your XML API (presumably you'll have a different
- # authentication scheme there anyway).
+ # 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.
+ #
+ # Since HTML and JavaScript requests are typically made from the browser, we
+ # need to ensure to verify request authenticity for the web browser. We can
+ # use session-oriented authentication for these types of requests, by using
+ # the `protect_from_forgery` method in our controllers.
#
# GET requests are not protected since they don't have side effects like writing
# to the database and don't leak sensitive information. JavaScript requests are
@@ -26,15 +31,21 @@ module ActionController #:nodoc:
# Ajax) requests are allowed to make GET requests for JavaScript responses.
#
# It's important to remember that XML or JSON requests are also affected and if
- # you're building an API you'll need something like:
+ # you're building an API you should change forgery protection method in
+ # <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
#
# class ApplicationController < ActionController::Base
# protect_from_forgery unless: -> { request.format.json? }
# end
#
- # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
- # which checks the token and resets the session if it doesn't match what was expected.
- # A call to this method is generated for new \Rails applications by default.
+ # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
+ # By default <tt>protect_from_forgery</tt> protects your session with
+ # <tt>:null_session</tt> method, which provides an empty session
+ # during request.
+ #
+ # We may want to disable CSRF protection for APIs since they are typically
+ # designed to be state-less. That is, the request API client will handle
+ # the session for you instead of Rails.
#
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
# value of this token must be added to every layout that renders forms by including
@@ -79,17 +90,19 @@ module ActionController #:nodoc:
#
# class FooController < ApplicationController
# protect_from_forgery except: :index
+ # end
#
# You can disable forgery protection on controller by skipping the verification before_action:
+ #
# skip_before_action :verify_authenticity_token
#
# Valid Options:
#
# * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. Like <tt>only: [ :create, :create_all ]</tt>.
- # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed proc or method reference.
+ # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
# * <tt>:prepend</tt> - By default, the verification of the authentication token is added to the front of the
# callback chain. If you need to make the verification depend on other callbacks, like authentication methods
- # (say cookies vs oauth), this might not work for you. Pass <tt>prepend: false</tt> to just add the
+ # (say cookies vs OAuth), this might not work for you. Pass <tt>prepend: false</tt> to just add the
# verification callback in the position of the protect_from_forgery call. This means any callbacks added
# before are run first.
# * <tt>:with</tt> - Set the method to handle unverified request.
@@ -125,17 +138,17 @@ module ActionController #:nodoc:
# This is the method that defines the application behavior when a request is found to be unverified.
def handle_unverified_request
request = @controller.request
- request.session = NullSessionHash.new(request.env)
- request.env['action_dispatch.request.flash_hash'] = nil
- request.env['rack.session.options'] = { skip: true }
- request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
+ request.session = NullSessionHash.new(request)
+ request.flash = nil
+ request.session_options = { skip: true }
+ request.cookie_jar = NullCookieJar.build(request, {})
end
protected
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
- def initialize(env)
- super(nil, env)
+ def initialize(req)
+ super(nil, req)
@data = {}
@loaded = true
end
@@ -149,14 +162,6 @@ module ActionController #:nodoc:
end
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
- def self.build(request)
- key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
- host = request.host
- secure = request.ssl?
-
- new(key_generator, host, secure, options_for_env({}))
- end
-
def write(*)
# nothing
end
@@ -247,7 +252,7 @@ module ActionController #:nodoc:
# Returns true or false if a request is verified. Checks:
#
- # * is it a GET or HEAD request? Gets should be safe and idempotent
+ # * Is it a GET or HEAD request? Gets should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
# * Does the X-CSRF-Token header match the form_authenticity_token
def verified_request?