shopify-api-js icon indicating copy to clipboard operation
shopify-api-js copied to clipboard

validateHmac returns false even if it is correct

Open Skriptach opened this issue 2 years ago • 6 comments

Issue summary

API method Shopify.Utils.validateHmac returns false even if query is correct

Reduced test case

import crypto from 'crypto';
import Shopify, { ApiVersion } from '@shopify/shopify-api';

const SHOPIFY_API_KEY = 'test';
const SHOPIFY_API_SECRET_KEY = 'secret';

const safeCompare = (hmac, queryStr) => (
  hmac === crypto.createHmac('SHA256', SHOPIFY_API_SECRET_KEY).update(queryStr).digest('hex')
);

const stringifyQuery = (obj) => Object.entries(obj)
  .filter((entry) => entry[0] !== 'hmac')
  .map((entry) => `${entry[0]}=${entry[1]}`)
  .join('&');

Shopify.Context.initialize({
  API_KEY: SHOPIFY_API_KEY,
  API_SECRET_KEY: SHOPIFY_API_SECRET_KEY,
  SCOPES: ['read_products'],
  HOST_NAME: 'example.com',
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  // This should be replaced with your preferred storage strategy
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

const url = new URL('https://example.com/?hmac=c0ffe062d215a3fa772aa0985dbae97db17a4c1996dc3f94ee1ae2bb097614e8&shop=sqd-dev.myshopify.com&timestamp=1639749203');
const query = Object.fromEntries(url.searchParams);
const { hmac, ...rest } = query;
const valid1 = Shopify.Utils.validateHmac(query);
const valid2 = safeCompare(hmac, stringifyQuery(rest));

console.log({ query, valid1, valid2 });

Expected behavior

valid1 should be true

Actual behavior

It is false

Skriptach avatar Dec 17 '21 14:12 Skriptach

I am also getting hmac validation as false for valid requests.

mkamalkayani avatar Feb 05 '22 01:02 mkamalkayani

any solution I am facing same error

Nikzero6 avatar Mar 06 '22 11:03 Nikzero6

I switched to a custom validator.

import crypto from "crypto";

export function validateHmac(query) {
  const safeCompare = (hmac: string, queryStr: string) => {
    return (
      hmac ===
      crypto
        .createHmac("SHA256", process.env.SHOPIFY_API_SECRET)
        .update(queryStr)
        .digest("hex")
    );
  };

  const params = new URLSearchParams(query);
  const hmac = params.get("hmac");
  params.delete("hmac");
  params.sort();

  return safeCompare(hmac, params.toString());
}

you can use it as follows

const isHmacValid = validateHmac(ctx.query);

mkamalkayani avatar Mar 06 '22 14:03 mkamalkayani

Same issue here

zirkelc avatar May 04 '22 07:05 zirkelc

I'm also having the same issue. Though the same code works on a one Shopify App but it fails on another Shopify App. Here is one of the example which is failing-

[Nest] 29512  - 05/11/2022, 1:49:42 AM     LOG [GET] /shopify/from-app-store?hmac=50d54a1742b32871db3284e39b39e9490e18b9edec91183403572320c9298986&host=Y29vZWUtZGVtby5teXNob3BpZnkuY29tL2FkbWlu&shop=cooee-demo.myshopify.com&timestamp=1652212222
checkAuthenticity:154 {
  hmac: '50d54a1742b32871db3284e39b39e9490e18b9edec91183403572320c9298986',
  host: 'Y29vZWUtZGVtby5teXNob3BpZnkuY29tL2FkbWlu',
  shop: 'cooee-demo.myshopify.com',
  timestamp: '1652212222'
}
checkAuthenticity:156 false 69ef84a9d228216d68cb942d63f37e712819e38712616914cdaea22b7c6b2283
checkAuthenticity:169 true 50d54a1742b32871db3284e39b39e9490e18b9edec91183403572320c9298986
[Nest] 29512  - 05/11/2022, 1:49:42 AM   DEBUG OperationFailedException- shopify.install.error.invalid-request

For a method like this-

checkAuthenticity(params: Record<string, any>): void {
        console.log('checkAuthenticity:154', params);
        const validHMAC = Shopify.Utils.validateHmac(params as AuthQuery);
        console.log('checkAuthenticity:156', validHMAC, generateLocalHmac(params as AuthQuery));

        const hmac = params.hmac;
        delete params.hmac;
        const queryString = Object.keys(params)
            .map(key => `${key}=${params[key]}`)
            .join('&');

        const createdHMAC = crypto.createHmac('sha256', this.configService.get('SHOPIFY_APP_API_SECRET'))
            .update(queryString)
            .digest('hex')
            .toString();
        console.log('checkAuthenticity:169', createdHMAC === hmac, createdHMAC);

        if (!validHMAC) {
            throw new OperationFailedException('shopify.install.error.invalid-request');
        }
    }

sagrawal31 avatar May 10 '22 20:05 sagrawal31

Same issue, getting it consistently only on one shop with latest 5.x.x version, others work fine. Probably a bug in Shopify library to validate HMAC on specific content. Sample fail

InvalidWebhookError: Could not validate request for topic products/update
X-Shopify-Webhook-Id
4d5c23b0-7800-49b1-83b1-c962a5b24eeb

mariusa avatar Aug 08 '22 11:08 mariusa

This issue is stale because it has been open for 90 days with no activity. It will be closed if no further action occurs in 14 days.

github-actions[bot] avatar Oct 08 '22 02:10 github-actions[bot]

not stale

mariusa avatar Oct 08 '22 06:10 mariusa

not stale

flowluap avatar Nov 16 '22 09:11 flowluap

This issue is stale because it has been open for 90 days with no activity. It will be closed if no further action occurs in 14 days.

github-actions[bot] avatar Jan 31 '23 02:01 github-actions[bot]

We are closing this issue because it has been inactive for a few months. This probably means that it is not reproducible or it has been fixed in a newer version. If it’s an enhancement and hasn’t been taken on since it was submitted, then it seems other issues have taken priority.

If you still encounter this issue with the latest stable version, please reopen using the issue template. You can also contribute directly by submitting a pull request– see the CONTRIBUTING.md file for guidelines

Thank you!

github-actions[bot] avatar Feb 14 '23 02:02 github-actions[bot]

Still not working. Someone also opened another similar issue https://github.com/Shopify/shopify-api-js/issues/878

https://github.com/Shopify/shopify-api-js/blob/2bd69f862c166c5892cc8a8ceb9eecff6aa9aeb1/lib/utils/processed-query.ts#L11

https://github.com/Shopify/shopify-api-js/blob/2bd69f862c166c5892cc8a8ceb9eecff6aa9aeb1/lib/utils/processed-query.ts#L50

As described in the mentioned issue, I suppose this does not work (for app proxy signature?) because the query is parsed to URLSearchParams, which when converted to string it will be joined with &. This differs from the reference implemention which joins the query strings without anything according to https://shopify.dev/docs/apps/online-store/app-proxies#calculate-a-digital-signature

Cerlancism avatar Jun 21 '23 11:06 Cerlancism

not stale, still not working.

embebe2021 avatar Jul 10 '23 04:07 embebe2021