How can I validate signature coming from Shopify while using application proxy?

New Member
2 0 1

I have created a custom app and configured application proxy URL. That feature is working OK, Shopify is forwarding the requests to the application server. For testing the application is logging all requests and all URL variables. These are the URL vars received so far  (app and path were changed to be generic)


[shop] => mycoolshop.myshopify.com
[path_prefix] => /apps/mycoolshopapp
[timestamp] => 1502934598
[signature] => be5e5baebd718478cbfe0a1ae997bbff4dae35fd90bb51b551b82dedf4bb98ce
 

In order to implement the correspondent security, I need to confirm that the request comes from Shopify so the idea is that if I can generate signature value on my application and that signature matches the one received from Shopify, then it can be considered as safe.

But even after following the tutorials, I'm not able to generate the exact value for the signature field. Using the same logic for generating hmac field (in oauth flow), I always get a different value. I take the variables in an associative array in PHP, remove signature key from array, sort by array key, and url encode the items before generating the hash. My app is running in PHP.

This is the string generated

$hmacSig = "path_prefix=%2Fapps%2Fmycoolshopapp&shop=mycoolshop.myshopify.com&timestamp=1502934945";

and the function used to generate the hash

return hash_hmac('sha256', $hmacSig , SHOPIFY_SECRET);

Could anyone please help me reviewing if I'm missing a field in the hmac signature? or maybe the logic for generating the signature is different than generating the hmac field?

Thanks for your help

0 Likes
Shopify Partner
25 0 4

Hi,

I don't have the example in php but below you can find an example in node.js.

 

var validateSignature = function(query) {
  var parameters = [];
  for (var key in query) {
    if (key != 'signature') {
      parameters.push(key + '=' + query[key])
    }
  }
  var message = parameters.sort().join('');
  var digest = crypto.createHmac('sha256', $ShopifyAppPassword).update(message).digest('hex');
  return digest === query.signature;
};

1. query parameter is : request.query property

2. $ShopifyAppPassword is your app password.

Basically when I am joining the array with an empty string instead of "&".

I hope that helps you out.

1 Like
Tourist
15 0 2

Hey DEV, 

Have you figured out how to fix that issue?

I'm having the same issue you were having. exactly.

0 Likes
Highlighted
New Member
2 0 1

Hi, I had to get the URL vars into an associative array, and remove the "signature" key. After that you sort the array

 

    ksort($vars);
    $params = [];
    foreach($vars as $key => $value)
        $params[] = "{$key}={$value}";

    $sig = implode("",$params);
    $calculatedSig = hash_hmac('sha256',$sig, "YOUR SHOPIFY SECRET");
  

The $calculatedSig var should match the signature received from shopify, Hope that helps

1 Like
Tourist
15 0 2

Thank you DEV, it worked exactly as you said.

+1 for the help mate.

For anyone looking into this in the future here is a working code for a proxy signature validation:

    function validateSignature($query, $shared_secret)
    {
        if(!is_array($query) || empty($query['signature']) || !is_string($query['signature']))
            return false;
         
        ksort($query); 
            
        $dataString = array();
        foreach ($query as $key => $value) {
            if($key != 'signature')
                $dataString[] = "{$key}={$value}";
        }
        
        $string = implode("", $dataString);
        
        if (version_compare(PHP_VERSION, '5.3.0', '>='))
            $signature = hash_hmac('sha256', $string, $shared_secret);
        else
            $signature = bin2hex(mhash(MHASH_SHA256, $string, $shared_secret));
        
        // secure compare        
        return hash_equals($query['signature'],$signature);
    }

1 Like
Tourist
3 0 0

For someone using the NodeJS version in Jayvin's code, you should ideally be using a time safe method of comparing hashes. The crypto module in NodeJS>6.6 comes with a function timingSafeEqual for doing that. So the comparison in the last line should be like this-

 

return crypto.timingSafeEqual(Buffer.from(digest),Buffer.from(query.signature));
0 Likes