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 and hanami-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

included(action) click to toggle source

@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

generate_csrf_token() click to toggle source

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_invalid_csrf_token(*, res) click to toggle source

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
invalid_csrf_token?(req, res) click to toggle source

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
missing_csrf_token?(req, *) click to toggle source

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(_req, res) click to toggle source

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_csrf_token(req, res) click to toggle source

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
verify_csrf_token?(req, *) click to toggle source

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