4
votes

I have a Rails application that is set to receive a webhook from WooCommerce. Specifically I am looking for when an order is created. I have tested and verified that it works when I have protect_from_forgery except create. Now I am trying to secure my application by verify the webhook. WooCommerce's documentation states the following secret is passed in the request header:

secret: an optional secret key that is used to generate a HMAC-SHA256 hash of the request body so the receiver can verify authenticity of the web hook

WooCommerce github doc

At the moment I am not sure how I am suppose to verify the request, then act on it. And if the request is not authorized reject it with a 401. Here is what I am trying:

class HooksController < ApplicationController

    protect_from_forgery
    before_action :restrict_access

    def order_created_callback
    ...
    end

    private

    SHARED_SECRET = 'my_secret_key'

    def verify_webhook(data, hmac_header)
        digest  = OpenSSL::Digest::Digest.new('sha256')
        calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, SHARED_SECRET, data)).strip
        calculated_hmac == hmac_header
    end

    def restrict_access
        data = request.body.read
        verified = verify_webhook(data, env["X-WC-Webhook-Signature"])
        head :unauthorized unless verified

    end
end

But so far I have been unsuccessful. Any input would be greatly appreciated. Thanks.

1
I can't offer a solution, but what you're doing seems completely sane. Most of the time I have this problem it's something dumb like a stray newline, but I see you're calling strip at the end. - Chuck Vose

1 Answers

5
votes

Ok I figured out the issue I was having. In case anyone else is trying to work with WooCommerce web hook it seems my issue was appropriately grabbing the header file of the request to match it against my calculated HMAC.

SHARED_SECRET = "my_secret"

def verify_webhook(data, hmac_header)
    hash  = OpenSSL::Digest::Digest.new('sha256')
    calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest(hash, SHARED_SECRET, data)).strip
    Rack::Utils.secure_compare(calculated_hmac, hmac_header)
end

def restrict_access
    data = request.body.read
    head = request.headers["X-WC-Webhook-Signature"]
    verified = verify_webhook(data, head)
    if verified
        return
    else
        render nothing: true, status: :unauthorized
    end
end