skip to Main Content

I’m working on developing a Shopify app. I’m trying to verify the Shopify transaction that happened on the app using Shopify’s HMAC authentication verification using this function. The webhook is created through the Shopify Webhook API for the topic app_subscriptions/update and shopifyApiSecret is the Client Secret for our Shopify app. Here’s the snippet of the code we’re using,

async function validateHmac(req: Request) {
  let shopifyApiSecret = config.shopifyAppSecret;

  let hmac: any = req.headers["x-shopify-hmac-sha256"];
  const message = JSON.stringify(req.body);
  const generatedHash = crypto
    .createHmac("sha256", shopifyApiSecret)
    .update(message)
    .digest("base64");
  console.log({ message, generatedHash, hmac });

  const signatureOk = crypto.timingSafeEqual(
    Buffer.from(generatedHash),
    Buffer.from(hmac)
  );
  if (signatureOk) {
    return true;
  } else {
    return false;
  }
}

We’ve tried comparing both the ways, that is with === as well as using timingSafeEqual, but the function always returns false and the generatedHash and hmac are not equal on inspection. Can anyone let me know if there is anything wrong with this implementation? Thanks in advance.

2

Answers


  1. Chosen as BEST ANSWER

    The issue was with the data we were consuming to generate the message. We were able to solve it by using the express function in our router

    express.json({
            limit: '10mb',
            verify: (req, _res, buf) => {
              (req as any).rawBody = buf;
            },
    });
    

    and passing the rawBody to the update function.

    async function validateHmac(req: Request) {
      let shopifyApiSecret = config.shopifyAppSecret;
    
      let hmac: any = req.headers["x-shopify-hmac-sha256"];
      const message = req.rawBody;
      const generatedHash = crypto
        .createHmac("sha256", shopifyApiSecret)
        .update(message)
        .digest("base64");
      console.log({ message, generatedHash, hmac });
    
      const signatureOk = crypto.timingSafeEqual(
        Buffer.from(generatedHash),
        Buffer.from(hmac)
      );
      if (signatureOk) {
        return true;
      } else {
        return false;
      }
    }
    

  2. Here is the implementation we have done in .Net. You can understand the logic from below code:

    string hmacHeader = Convert.ToString(Request.Headers["X-Shopify-Hmac-SHA256"]);
    string requestJson = JsonSerializer.Serialize(request);
    
    HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(APISecretKey));
    string hash = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(requestJson)));
    
    isSuccess = string.Equals(hmacHeader, hash, StringComparison.OrdinalIgnoreCase);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search