shaune
shaune

Reputation: 1030

Issues Matching WooCommerce WebHook signature on Node.JS

I am attempting to a validate the webhook transaction from WooCommerce on my Node.js website. However I cannot get the 2 strings to match.

I can see that the php signature is generated with the following code, and the source can be viewed here WooCommerce Source.

base64_encode( hash_hmac( $hash_algo, $payload, $this->get_secret(), true ) ));

I have noticed that if i turn off true on the hash_hmac, I can then get the 2 systems to create a match, however I would rather not edit the core of WooCommerce so I am wondering if there is something I am missing here?

For my Example I did edit the core and forced the payload to be the following, just so i could easily try and match the 2 systems

payload = '{"id":1,"etc":2,"host":"http:/\/localhost\/view-order\/8"}'
secret = 'welcome'

My code in Node.Js is the following.

var crypto = require('crypto');    

hmac = crypto.createHmac('sha256', secret);
hmac.setEncoding('binary');
hmac.write(payload);
hmac.end();
hash = hmac.read();

result = base64.encode(hash);

console.log(result);

If I remove the url from the "host" JSON then it does work, is it something to do with the way it has been escaped? I think it may be an issue with the way PHP and node do the SHA256 hashing. I really can't workout exactly how to solve this.

Any help would be great,

Thanks

Upvotes: 2

Views: 2588

Answers (2)

Mitanshu
Mitanshu

Reputation: 869

Extending @genau's answer:

You may ask, why is the issue happening!?
Almost all of us add the below code in our express app, which manipulates the actual req.body, causing issues in authenticating hash signatures.

app.use(express.json());

How to handle this issue?
Add original req.body to a new field (for eg: req.rawBody), and use this field instead of req.body while verifying the signature.

// This code will copy the req.body data to req.rawBody
app.use(
  express.json({
    verify: function (req, res, buf) {
      req.rawBody = buf;
    },
  })
);

Later, you can verify the signature as shown below:

function requestHandler (req, res) {
    const incomingSignature = req.headers["x-wc-webhook-signature"]; // modify this accordingly
    const payload = req.rawBody; // Buffer, already, no need to stringify.

    const calculatedSignature = crypto
        .createHmac("sha256", "your-secret-key")
        .update(payload)
        .digest("base64");

    const isValid = calculatedSignature === incomingSignature;
  
    // ...
}

Thanks!

Upvotes: 0

genau
genau

Reputation: 194

I have run into a similar issue as you, using the code suggested here: SHA256 webhook signature from WooCommerce never verifies

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;
}

(Where body comes from req.body).

This only started working for me when I changed the way I obtain the raw body. I got it using the bodyParser middleware:
app.use(bodyParser.json({verify:function(req,res,buf){req.rawBody=buf}}))
(As explained in: https://github.com/expressjs/body-parser/issues/83#issuecomment-80784100)

So now instead of using new Buffer(JSON.stringify(body), 'utf8') I just use req.rawBody
I hope this solves your problems too.

Upvotes: 4

Related Questions