Rack Request Verification?
I can't seem to use Rack requests (e.g. from Rails) directly in the Verification as they expose headers on a headers accessor rather than on the base object itself.
I have created a pretty dirty monkey-patch to check if there is a headers method and check that first but was wondering if there is a better option?
@waynerobinson I actually ran into the same issue 6 years after (lol). Thanks for opening this issue in the first place, it helped me implement a workaround:
Add Monkey Patches
# lib/ext/http_signatures/signing_string.rb
module HttpSignatures
class SigningString
def header_value(header)
return request_target if header == REQUEST_TARGET
if @message.respond_to?(:headers)
@message.headers.fetch(header) { raise HeaderNotInMessage, header }
else
@message.fetch(header) { raise HeaderNotInMessage, header }
end
end
# @message.try(:original_fullpath) added to take in account query params
# since request.path (aka @message.path) does not include them.
def request_target
"%s %s" % [@message.method.downcase, @message.try(:original_fullpath) || @message.path]
end
end
end
# lib/ext/http_signatures/verification.rb
module HttpSignatures
class Verification
private
def signature_header_present?
if @message.respond_to?(:headers)
@message.headers.key?('Signature')
else
@message.key?('Signature')
end
end
def fetch_header(name)
if @message.respond_to?(:headers)
@message.headers.fetch(name)
else
@message.fetch(name)
end
end
end
end
Load Monkey Patches
# config/initializers/ext.rb
Dir.glob(Rails.root.join('lib', 'ext', '**', '*.rb')).each do |file|
require file
end
Profit
class CheckoutEventsController
skip_before_action :verify_authenticity_token
before_action :verify_signature, unless: -> { Rails.env.test? }
def incoming
render json: { success: true }, status: :ok
end
def verify_signature
context = HttpSignatures::Context.new(
keys: { 'api_secret' => Bold.config.fetch(:client_secret) },
algorithm: 'hmac-sha256'
)
unless context.verifier.valid?(request)
render json: { success: false }, status: :unprocessable_entity
end
end
end
Some Notes
- I noticed that for a Signature using only the header date:
"keyId=\"api_secret\",algorithm=\"hmac-sha256\",headers=\"date\",signature=\"uBCAy4G4YvmP92iyCvG+jhmbm8Pb0yolxh8S8HnZt2U=\""
you can do that without any Monkey Patches:
class CheckoutEventsController
skip_before_action :verify_authenticity_token
before_action :verify_signature, unless: -> { Rails.env.test? }
def incoming
render json: { success: true }, status: :ok
end
def verify_signature
context = HttpSignatures::Context.new(
keys: { 'api_secret' => Bold.config.fetch(:client_secret) },
algorithm: 'hmac-sha256'
)
unless context.verifier.valid?(request.headers) # You can just pass headers and it will work.
render json: { success: false }, status: :unprocessable_entity
end
end
end
Problems start to rise when the headers include (request-target).
-
@pda Obviously I'm not a huge fan of my workaround and feel like there must be a better way™, would you mind demonstrating us how to use HttpSignatures with Rails without any monkey patches?
-
@pda Thanks for the gem! ;)