http-signatures-ruby icon indicating copy to clipboard operation
http-signatures-ruby copied to clipboard

Rack Request Verification?

Open waynerobinson opened this issue 10 years ago • 1 comments

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 avatar Oct 02 '15 04:10 waynerobinson

@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

  1. 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).

  1. @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?

  2. @pda Thanks for the gem! ;)

GesJeremie avatar Jan 29 '21 04:01 GesJeremie