Nodejs - Expressjs - 验证 shopify webhook

问题描述 投票:0回答:5

我正在尝试验证从开发环境中的

hmac
shopify
发送的
webhook
代码。但是
shopify
不会将
webhook
的发布请求发送到非实时端点,因此我使用 requestbin 捕获请求,然后使用
postman
将其发送到我的本地网络服务器。

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

我的验证 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);
});
node.js shopify hmac webhooks
5个回答
7
投票

我的做法有点不同——不确定我在哪里看到了建议,但我在正文解析器中进行了验证。 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';
    }
 }})); 

然后任何网络钩子只处理经过验证的标头:

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

3
投票

简短回答

express 中的主体解析器不能很好地处理 BigInt,并且像订单号这样作为整数传递的东西会被损坏。除此之外,某些值被编辑,例如 URL 最初发送为“https://...”,OP 也从其他代码中发现了这一点。

要解决此问题,请不要使用主体解析器解析数据,而是将其作为原始字符串获取,稍后您可以使用 json-bigint 解析它以确保它没有被损坏。

长答案

虽然 @bknights 的 answer 工作得很好,但首先找出发生这种情况的原因很重要。

对于我在 Shopify 的“order_created”事件上创建的 webhook,我发现传递给正文的请求 id 与我从测试数据发送的内容不同,结果证明这是正文的问题 - Express 中的解析器不能很好地处理大整数。

最终,我将一些东西部署到 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}));

基于这个答案

我后来使用 json-bigint 解析 rawBody 以在其他地方的代码中使用,否则某些数字已损坏。


1
投票

// 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');
    }

}


0
投票

只是想分享一个简短的说明来表达我是多么喜欢你的博客文章!您对 Shopify Webhook 的建议非常准确,对我很有帮助。哦,如果您渴望了解有关该主题的更多见解,请查看此页面。阅读愉快! https://expertguru.ai/blogs/tech-insights/optimizing-shopify-webhook-performance-expertguru-best-practices-guide


-1
投票

有同样的问题。使用 request.rawBody 而不是 request.body 有帮助:

import Router from "koa-router";
import koaBodyParser from "koa-bodyparser";
import crypto from "crypto";

...

koaServer.use(koaBodyParser()); 

...

koaRouter.post(
    "/webhooks/<yourwebhook>",
    verifyShopifyWebhooks,
    async (ctx) => {
      try {
        ctx.res.statusCode = 200;
      } catch (error) {
        console.log(`Failed to process webhook: ${error}`);
      }
    }
);

...

async function verifyShopifyWebhooks(ctx, next) {
  const generateHash = crypto
    .createHmac("sha256", process.env.SHOPIFY_WEBHOOKS_KEY) // that's not your Shopify API secret key, but the key under Webhooks section in your admin panel (<yourstore>.myshopify.com/admin/settings/notifications) where it says "All your webhooks will be signed with [SHOPIFY_WEBHOOKS_KEY] so you can verify their integrity
    .update(ctx.request.rawBody, "utf-8")
    .digest("base64");

  if (generateHash !== shopifyHmac) {
    ctx.throw(401, "Couldn't verify Shopify webhook HMAC");
  } else {
    console.log("Successfully verified Shopify webhook HMAC");
  }
  await next();
}

© www.soinside.com 2019 - 2024. All rights reserved.