Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support CSRF token retrieval from header "X-CSRF-Token" #422

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions lib/hanami/action/csrf_protection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ class Action
#
# It stores a "challenge" token in session. For each "state changing request"
# (eg. <tt>POST</tt>, <tt>PATCH</tt> etc..), we should send a special param:
# <tt>_csrf_token</tt>.
# <tt>_csrf_token</tt> which contain the "challenge" token.
#
# We can specify how to retreive the token from the request, by overriding <tt>#request_csrf_token</tt>.
# This is useful if we want to handle XMLHttpRequest (XHR) requests.
#
# If the param matches with the challenge token, the flow can continue.
# Otherwise the application detects an attack attempt, it reset the session
Expand Down Expand Up @@ -63,6 +66,21 @@ class Action
# end
# end
# end
#
# @example Custom Token Retrieval
# module Web::Controllers::Books
# class Create < Web::Action
# def handle(*)
# # ...
# end
#
# private
#
# def request_csrf_token(req)
# req.get_header('X-CSRF-Token')
# end
# end
# end
module CSRFProtection
# Session and params key for CSRF token.
#
Expand Down Expand Up @@ -107,6 +125,34 @@ def set_csrf_token(_req, res)
res.session[CSRF_TOKEN] ||= generate_csrf_token
end

# Get CSRF Token from request.
#
# By default retreives the token from the request param <tt>_csrf_token</tt>.
#
# Override this method, for custom handling of the request token retrieval.
#
# @since 2.X.X
Copy link
Author

@masterT masterT Apr 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should the version be?

#
# @api private
#
# @example Custom Token Retrieval
# module Web::Controllers::Books
# class Create < Web::Action
# def handle(*)
# # ...
# end
#
# private
#
# def request_csrf_token(req)
# req.get_header('X-CSRF-Token')
# end
# end
# end
def request_csrf_token(req)
req.params[CSRF_TOKEN]
end

# Verify if CSRF token from params, matches the one stored in session.
# If not, it raises an error.
#
Expand All @@ -131,14 +177,14 @@ 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[CSRF_TOKEN])
!::Rack::Utils.secure_compare(req.session[CSRF_TOKEN], request_csrf_token(req))
end

# Verify the CSRF token was passed in params.
#
# @api private
def missing_csrf_token?(req, *)
Hanami::Utils::Blank.blank?(req.params[CSRF_TOKEN])
def missing_csrf_token?(req, res)
Hanami::Utils::Blank.blank?(request_csrf_token(req))
end

# Generates a random CSRF Token
Expand Down