Nodejs - Expressjs - 验证shopify webhook

时间:2016-04-20 13:22:12

标签: node.js shopify hmac webhooks

我正在尝试验证在开发环境中从hmac shopify发送的webhook代码。但是,shopify不会将webhook的发布请求发送到非实时终端,因此我使用requestbin来捕获请求,然后使用postman将其发送到我的本地网络服务器。

从shopify documentation,我似乎做了一切正确的事情,并尝试应用node-shopify-auth verifyWebhookHMAC function中使用的方法。但到目前为止,这一切都没有奏效。 代码永远不会匹配。 我在这里做错了什么?

我验证webhook的代码:

 function verifyWebHook(req, res, next) {
      var message = JSON.stringify(req.body);
    //Shopify seems to be escaping forward slashes when the build the HMAC
        // so we need to do the same otherwise it will fail validation
        // Shopify also seems to replace '&' with \u0026 ...
        //message = message.replace('/', '\\/');
        message = message.split('/').join('\\/');
    message = message.split('&').join('\\u0026');
      var signature = crypto.createHmac('sha256', shopifyConfig.secret).update(message).digest('base64');
      var reqHeaderHmac = req.headers['x-shopify-hmac-sha256'];
      var truthCondition = signature === reqHeaderHmac;

      winston.info('sha256 signature: ' + signature);
      winston.info('x-shopify-hmac-sha256 from header: ' + reqHeaderHmac);
      winston.info(req.body);

      if (truthCondition) {
        winston.info('webhook verified');
        req.body = JSON.parse(req.body.toString());
        res.sendStatus(200);
        res.end();
        next();
      } else {
        winston.info('Failed to verify web-hook');
        res.writeHead(401);
        res.end('Unverified webhook');
      }
    }

收到请求的路线:

router.post('/update-product', useBodyParserJson, verifyWebHook, function (req, res) {
  var shopName = req.headers['x-shopify-shop-domain'].slice(0, -14);
  var itemId = req.headers['x-shopify-product-id'];
  winston.info('Shopname from webhook is: ' + shopName + ' For item: ' + itemId);
});

3 个答案:

答案 0 :(得分:3)

我做的有点不同 - 不确定我在哪里看到推荐,但我在身体解析器中进行验证。 IIRC的一个原因是,在任何其他处理者可能触及它之前,我可以访问原始身体:

app.use( bodyParser.json({verify: function(req, res, buf, encoding) {
    var shopHMAC = req.get('x-shopify-hmac-sha256');
    if(!shopHMAC) return;
    if(req.get('x-kotn-webhook-verified')) throw "Unexpected webhook verified header";
    var sharedSecret = process.env.API_SECRET;
    var digest = crypto.createHmac('SHA256', sharedSecret).update(buf).digest('base64');
    if(digest == req.get('x-shopify-hmac-sha256')){
        req.headers['x-kotn-webhook-verified']= '200';
    }
 }})); 

然后任何Web挂钩只处理经过验证的标头:

if('200' != req.get('x-kotn-webhook-verified')){
    console.log('invalid signature for uninstall');
    res.status(204).send();
    return;
}
var shop = req.get('x-shopify-shop-domain');
if(!shop){
    console.log('missing shop header for uninstall');
    res.status(400).send('missing shop');
    return;
}

答案 1 :(得分:1)

简答

express中的body解析器不能很好地处理BigInt,并且作为整数传递的订单号之类的东西被破坏了。除了编辑某些值,例如URL最初发送为“https:// ...”,OP也从其他代码中找到。

要解决此问题,请不要使用正文解析器解析数据,而是将其作为原始字符串进行解析,稍后您可以使用json-bigint对其进行解析,以确保它们都没有损坏。

长答案

尽管@bknights的answer工作得很好,但重要的是要找出为什么会发生这种情况。

对于我在Shopify的“order_created”事件中做的webhook,我发现传递给正文的请求的ID与我从测试数据发送的不同,这结果是一个问题快递中的身体解析器,它与大整数一起玩不好。

最终我正在为Google云功能部署一些东西,而且req已经拥有了我可以使用的原始主体,但是在我的测试环境中我在Node中实现了以下作为单独的主体解析器,因为使用相同的主体解析器两次覆盖原始身体与JSON

var rawBodySaver = function (req, res, buf, encoding) {
    if (buf && buf.length) {
      req.rawBody = buf.toString(encoding || 'utf8');
    }
}
app.use(bodyParser.json({verify: rawBodySaver, extended: true}));

基于this回答

我稍后使用json-bigint解析rawBody以用于其他地方的代码,否则一些数字已被破坏。

答案 2 :(得分:0)

// Change the way body-parser is used
const bodyParser = require('body-parser');

var rawBodySaver = function (req, res, buf, encoding) {
    if (buf && buf.length) {
        req.rawBody = buf.toString(encoding || 'utf8');
    }
}
app.use(bodyParser.json({ verify: rawBodySaver, extended: true }));


// Now we can access raw-body any where in out application as follows
// request.rawBody in routes;

// verify webhook middleware
const verifyWebhook = function (req, res, next) {
    console.log('Hey!!! we got a webhook to verify!');

    const hmac_header = req.get('X-Shopify-Hmac-Sha256');
    
    const body = req.rawBody;
    const calculated_hmac = crypto.createHmac('SHA256', secretKey)
        .update(body,'utf8', 'hex')
        .digest('base64');

    console.log('calculated_hmac', calculated_hmac);
    console.log('hmac_header', hmac_header);

    if (calculated_hmac == hmac_header) {
        console.log('Phew, it came from Shopify!');
        res.status(200).send('ok');
        next();
    }else {
        console.log('Danger! Not from Shopify!')
        res.status(403).send('invalid');
    }

}