module Hanami::Action::CSRFProtection
CSRF Protection
This security mechanism is enabled automatically if sessions are turned on.
It stores a “challenge” token in session. For each “state changing request” (eg. POST
, PATCH
etc..), we should send a special param: _csrf_token
.
If the param matches with the challenge token, the flow can continue. Otherwise the application detects an attack attempt, it reset the session and Hanami::Action::InvalidCSRFTokenError
is raised.
We can specify a custom handling strategy, by overriding #handle_invalid_csrf_token
.
Form helper (#form_for
) automatically sets a hidden field with the correct token. A special view method (#csrf_token
) is available in case the form markup is manually crafted.
We can disable this check on action basis, by overriding #verify_csrf_token?
.
@since 0.4.0
@see www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29 @see www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
@example Custom Handling
module Web::Controllers::Books class Create < Web::Action def handl(*) # ... end private def handle_invalid_csrf_token Web::Logger.warn "CSRF attack: expected #{ session[:_csrf_token] }, was #{ params[:_csrf_token] }" # manual handling end end end
@example Bypass Security Check
module Web::Controllers::Books class Create < Web::Action def handle(*) # ... end private def verify_csrf_token?(req, res) false end end end
Constants
- CSRF_TOKEN
Session
and params key for CSRF token.This key is shared with
hanami-controller
andhanami-helpers
@since 0.4.0 @api private
- IDEMPOTENT_HTTP_METHODS
Idempotent HTTP methods
By default, the check isn’t performed if the request method is included in this list.
@since 0.4.0 @api private
Public Class Methods
@since 0.4.0 @api private
# File lib/hanami/action/csrf_protection.rb, line 91 def self.included(action) unless Hanami.respond_to?(:env?) && Hanami.env?(:test) action.include Hanami::Action::Session action.class_eval do before :set_csrf_token, :verify_csrf_token end end end
Private Instance Methods
Generates a random CSRF Token
@since 0.4.0 @api private
# File lib/hanami/action/csrf_protection.rb, line 148 def generate_csrf_token SecureRandom.hex(32) end
Handle CSRF attack.
The default policy resets the session and raises an exception.
Override this method, for custom handling.
@raise [Hanami::Action::InvalidCSRFTokenError]
@since 0.4.0
@example
module Web::Controllers::Books class Create < Web::Action def call(*) # ... end private def handle_invalid_csrf_token(req, res) # custom invalid CSRF management goes here end end end
# File lib/hanami/action/csrf_protection.rb, line 200 def handle_invalid_csrf_token(*, res) res.session.clear raise InvalidCSRFTokenError end
Verify if CSRF token from params, matches the one stored in session.
Don’t override this method.
@since 0.4.0 @api private
# File lib/hanami/action/csrf_protection.rb, line 130 def invalid_csrf_token?(req, res) return false unless verify_csrf_token?(req, res) missing_csrf_token?(req, res) || !::Rack::Utils.secure_compare(req.session[CSRF_TOKEN], req.params.raw[CSRF_TOKEN.to_s]) end
Verify the CSRF token was passed in params.
@api private
# File lib/hanami/action/csrf_protection.rb, line 140 def missing_csrf_token?(req, *) Hanami::Utils::Blank.blank?(req.params.raw[CSRF_TOKEN.to_s]) end
Set CSRF Token in session
@since 0.4.0 @api private
# File lib/hanami/action/csrf_protection.rb, line 106 def set_csrf_token(_req, res) res.session[CSRF_TOKEN] ||= generate_csrf_token end
Verify if CSRF token from params, matches the one stored in session. If not, it raises an error.
Don’t override this method.
To bypass the security check, please override #verify_csrf_token?
. For custom handling of an attack, please override #handle_invalid_csrf_token
.
@since 0.4.0 @api private
# File lib/hanami/action/csrf_protection.rb, line 120 def verify_csrf_token(req, res) handle_invalid_csrf_token(req, res) if invalid_csrf_token?(req, res) end
Decide if perform the check or not.
Override and return false
if you want to bypass security check.
@since 0.4.0
@example
module Web::Controllers::Books class Create < Web::Action def call(*) # ... end private def verify_csrf_token?(req, res) false end end end
# File lib/hanami/action/csrf_protection.rb, line 172 def verify_csrf_token?(req, *) !IDEMPOTENT_HTTP_METHODS[req.request_method] end