SHA256 webhook signature from WooCommerce never verifies

后端 未结 6 863
再見小時候
再見小時候 2021-01-02 23:47

I am receiving webhooks from a woocommerce site into a nodejs/express application. I am trying to verify the webhook\'s signature to prove authenticity, yet the hash I compu

相关标签:
6条回答
  • 2021-01-03 00:11

    Since this is the top Google result for this question and there isn't a complete answer out there, here's a Python version using Flask that validates the WooCommerce webhook signature. It took a bit of trial and error, hope it helps someone out there:

    import json
    import base64
    import hmac
    import hashlib
    
    from flask import Flask, request, Response
    
    app = Flask(__name__)
    
    # The WooCommerce webhook secret
    WEBHOOK_SECRET = 'abc123456'
    
    # Function that compares the computed signature to the one in the request
    def verify_woocommerce_signature(body, signature, secret):
        digest = hmac.new(bytes(secret, 'utf-8'), body, hashlib.sha256).digest()
        encoded = base64.b64encode(digest).decode()
    
        return encoded == signature
    
    # WooCommerce Order Creation Event
    @app.route('/webhooks/woocommerce/order_created', methods=['POST'])
    def webhooks_woocommerce_order_created():
        # Get raw request body
        body = request.get_data()
        
        # Get request signature
        signature = request.headers['X-WC-WEBHOOK-SIGNATURE']
        
        # Verify webhook signature and handle mismatch
        if verify_woocommerce_signature(body, signature, WEBHOOK_SECRET) is False:
            msg = {"success": False}
            return Response(json.dumps(msg), status=400, mimetype='application/json')
    
        # Signatures match, process the payload
    
    0 讨论(0)
  • 2021-01-03 00:18

    Hope to save someone time, below works for me.

    // Make sure to add a WISTIA_SECRET_KEY in your Environment Variables
    // See https://docs.pipedream.com/environment-variables/
    const secret = process.env.SELF_AUTOMATE_KEY;
    const signature = event.headers["x-wc-webhook-signature"];
    const body = steps.trigger.raw_event["body_b64"];
    const clean_Body = body.replace("body_b64: ", "");
    //const body = steps.trigger.raw_event;
    console.log(event.headers["x-wc-webhook-signature"]);
    
    console.log("Print Body", clean_Body);
    
    if (process.env.SELF_AUTOMATE_KEY === undefined) {
      $end("No WISTIA_SECRET_KEY environment variable defined. Exiting.")
    }
    
    if (!("x-wc-webhook-signature" in event.headers)) {
      $end("No x-wc-webhook-signature header present in the request. Exiting.")
    }
    
    // Once we've confirmed we have a signature, we want to 
    // validate it by generating an HMAC SHA-256 hexdigest
    const crypto = require('crypto');
    
    const hash = crypto.createHmac('sha256',
      secret).update(JSON.stringify(clean_Body), 'base64').digest('base64');
    
    
    
    console.log(hash);
    // $end() ends the execution of a pipeline, presenting a nice message in the "Messages"
    // column in the inspector above. See https://docs.pipedream.com/notebook/code/#end
    if (hash !== signature) {
      $end("The correct secret key was not passed in the event. Exiting!")
    }
    
    0 讨论(0)
  • 2021-01-03 00:24

    Hash must be calculated over the 'raw body'. When used in an 'express application' and using JSON bodyParser middleware 'raw body' is lost, see How to access the raw body of the request before bodyparser? to hold-on to the 'raw body'.

    For example:

    // 'misuse' verify option  
    app.use(bodyParser.json({
      verify: function(req,res,buf) { 
        req.rawBody=buf; 
      }
    }));
    
    var wcSignature = req.get('X-Wc-Webhook-Signature');
    debug('wc signature: %s', wcSignature);
    var calculatedSignature = crypto.createHmac('SHA256', secret)
      .update(req.rawBody, 'utf8')
      .digest('base64');
    debug('calculated signature: %s', calculatedSignature);
    
    0 讨论(0)
  • 2021-01-03 00:30

    For people using node, this should do the trick.

    var processWebHookSignature = function (secret, body, signature) {
      signatureComputed = crypto.createHmac('SHA256', secret).update(
        new Buffer(JSON.stringify(body), 'utf8')).digest('base64');
    
      return ( signatureComputed === signature ) ? true : false;
    }
    
    0 讨论(0)
  • 2021-01-03 00:34

    Old question but maybe it helps some poor soul out there. The signature needs to be checked against the body and not the JSON it contains. i.e. the raw bytes of the body.

    pseudo:

            byte[] body = request.Body;
            string signature = request.Header["X-WC-Webhook-Signature"];
    
            byte[] secretUtf8 = GetUtf8Bytes("yoursecrethere");
            byte[] hash = HMAC_SHA256.ComputeHash(body, secretUtf8);
            string hashBase64 = ToBase64String(hash);
    
            bool isValid = hashBase64 == signature;
    
    0 讨论(0)
  • 2021-01-03 00:36

    I stumbled upon this while searching for a solution to have an Asp.NET application check signature of the Woocommerce web hook. My answer is based on the pseudo code Johannes provided which worked great. I implemented a custom controller attribute to intercept the request and check the signature before it hits the API controller method:

    public class HmacSignatureFilter : ActionFilterAttribute
    {
    
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var requestContent = actionContext.Request.Content;
            var jsonContent = requestContent.ReadAsStringAsync().Result;
            var byteContent = requestContent.ReadAsByteArrayAsync().Result;
    
            //if the request contains this, it's the verification request from Woocommerce
            //when the webhook is created so let it pass through so it can be verified
            if (!jsonContent.Contains("webhook_id"))
            {
                var requestSignature = actionContext.Request.Headers;
    
                var bodyHash = HashHMAC("test", byteContent); //this is the shared key between Woo and custom API.  should be from config or database table.
    
                var signature = actionContext.Request.Headers.GetValues("x-wc-webhook-signature");
    
                if (bodyHash != signature.FirstOrDefault())
                {
                    throw new HttpResponseException(HttpStatusCode.Forbidden);
                }
            }
    
            base.OnActionExecuting(actionContext);
        }
    
    
        private static string HashHMAC(string key, byte[] message)
        {
            var keyBytes = Encoding.UTF8.GetBytes(key);
            var hash = new HMACSHA256(keyBytes);
    
            var computedHash = hash.ComputeHash(message);
            return Convert.ToBase64String(computedHash);
        }
    }
    

    Then to use the filter in your Api controller:

    [RoutePrefix("api/woo")]
    public class WooController : ApiController
    {
    
        public SomeService _service;
    
        public WooController()
        {
            this._service = new SomeService();
        }
    
        // POST api/values
        [Route("orderCreated")]
        [HttpPost]
        [HmacSignatureFilter]
        public string Post()
        {
            var requestContent = Request.Content;
            var jsonContent = requestContent.ReadAsStringAsync().Result;
    
            //this is the test request from Woocommerce.  Don't do anything but 
            //respond so it can verify the endpoint
            if (jsonContent.Contains("webhook_id"))
            {
                return "Webhook Test Success";
            }
    
            var wooOrder = JsonConvert.DeserializeObject<WooOrderModel>(jsonContent);
    
            //call a service to use the order data provided by WooCommerce
            _service.AddOrder(wooOrder);
    
            return "Success";
        }
    
    }
    

    Note: Hashing code was referenced from this SO post.

    0 讨论(0)
提交回复
热议问题