验证嵌入式应用程序设置页面的访问来自PHP中的Shopify

时间:2018-08-30 06:11:32

标签: php shopify hmac

当客户端安装应用程序时,他们可以选择在/admin/apps页面上的应用程序列表中单击应用程序名称。

当他们单击该页面时,我的应用程序的PHP索引文件会收到以下$_GET变量:

hmac = some_long_alphanumaeric_hmac
locale = en
protocol = https://
shop = example-shop.myshopify.com
timestamp = 1535609063

要验证来自Shopify的Webhook,我可以成功使用此功能:

function verify_webhook($data, $hmac_header, $app_api_secret) {
    $calculated_hmac = base64_encode(hash_hmac('sha256', $data, $app_api_secret, true));
    return ($hmac_header == $calculated_hmac);
}

// Set vars for Shopify webhook verification
$hmac_header = $_SERVER['HTTP_X_SHOPIFY_HMAC_SHA256'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header, MY_APP_API_SECRET);

是否可以验证来自安装了该应用程序的Shopify客户端的应用程序管理员页面访问?

PS:我已经仔细研究了Embedded Apps API和{{3}两者(但我不知道这是否是正确的文档,或者我做错了什么)。 }(其中没有有关如何验证嵌入式应用程序管理员页面访问的说明)。

更新:

我尝试了其他各种方法,在此过程中发现了一些荒谬的问题,但还是没有运气。

  1. 应使用我理解的方法来验证Shopify HMAC是否类似于此:

    function verify_hmac($hmac = NULL, $shopify_app_api_secret) {
        $params_array = array();
        $hmac = $hmac ? $hmac : $_GET['hmac'];
        unset($_GET['hmac']);
    
        foreach($_GET as $key => $value){
            $key = str_replace("%","%25",$key);
            $key = str_replace("&","%26",$key);
            $key = str_replace("=","%3D",$key);
            $value = str_replace("%","%25",$value);
            $value = str_replace("&","%26",$value);
            $params_array[] = $key . "=" . $value;
        }
    
        $params_string = join('&', $params_array);
        $computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret);
    
        return hash_equals($hmac, $computed_hmac);
    }
    

但是行$params_string = join('&', $params_array);通过将&timestamp编码为xtamp导致了一个令人讨厌的问题……使用http_build_query($params_array)会导致同样荒谬的事情。发现其他人有同样的问题GitHub example provided。基本上可以通过将&编码为&来解析为$params_string = join('&', $params_array);

  1. 我的最终版本是这样的,但仍然无法正常工作(所有注释的代码是我尝试的其他无效代码):

    function verify_hmac($hmac = NULL, $shopify_app_api_secret) {
        $params_array = array();
        $hmac = $hmac ? $hmac : $_GET['hmac'];
        unset($_GET['hmac']);
    //  unset($_GET['protocol']);
    //  unset($_GET['locale']);
    
        foreach($_GET as $key => $value){
            $key = str_replace("%","%25",$key);
            $key = str_replace("&","%26",$key);
            $key = str_replace("=","%3D",$key);
            $value = str_replace("%","%25",$value);
            $value = str_replace("&","%26",$value);
            $params_array[] = $key . "=" . $value;
    //  This commented out method below was an attempt to see if 
    //  the imporperly encoded query param characters were causing issues
    /*
            if (!isset($params_string) || empty($params_string)) {
                $params_string = $key . "=" . $value;
            }
            else {
                $params_string = $params_string . "&" . $key . "=" . $value;
            }
    */
        }
    
    //  $params_string = join('&', $params_array);
    //  echo $params_string;
    //  $computed_hmac =  base64_encode(hash_hmac('sha256', $params_string, $shopify_app_api_secret, true));
    //  $computed_hmac =  base64_encode(hash_hmac('sha256', $params_string, $shopify_app_api_secret, false));
    //  $computed_hmac =  hash_hmac('sha256', $params_string, $shopify_app_api_secret, false);
    //  $computed_hmac =  hash_hmac('sha256', $params_string, $shopify_app_api_secret, true);
        $computed_hmac = hash_hmac('sha256', http_build_query($params_array), $shopify_app_api_secret);
    
        return hash_equals($hmac, $computed_hmac);
    }
    

3 个答案:

答案 0 :(得分:0)

如果您从Shopify获得成功,那么您要做的第一件事就是在持久层中检查是否已注册商店。如果您这样做了,并且进行了某种形式的设置,则可以自由地将应用程序呈现给该商店。如果您没有让商店持续存在,那么请经历oAuth周期,以获取要在商店上使用的身份验证令牌,该令牌将与商店和新会话一起保留。

对于商店中要接收Webhook的任何路线或终点,这些请求当然都没有会话,因此您可以使用HMAC安全方法来确定要做什么。因此,您的问题显然是跨越两个不同的概念,每个概念的处理方式都不相同。有关差异的文档非常清楚。

答案 1 :(得分:0)

以下是相关文档:https://shopify.dev/tutorials/authenticate-with-oauth#verification。 Sandeep的此信息也非常有用:https://community.shopify.com/c/Shopify-APIs-SDKs/HMAC-verify-app-install-request-using-php/m-p/140097#comment-253000

这对我有用:

function verify_visiter() // returns true or false
{
  // check that timestamp is recent to ensure that this is not a 'replay' of a request that has been intercepted previously (man in the middle attack)
  if (!isset($_GET['timestamp'])) return false;
    $seconds_in_a_day = 24 * 60 * 60;
    $older_than_a_day = $_GET['timestamp'] < (time() - $seconds_in_a_day);
    if ($older_than_a_day) return false;
  $shared_secret = Your_Shopify_app_shared_secret;
  $hmac_header = $_GET['hmac'];
  unset($_GET['hmac']);
  $data = urldecode(http_build_query($_GET));
  $calculated_hmac = hash_hmac('sha256', $data, $shared_secret, false);
  return hash_equals($hmac_header, $calculated_hmac);
}

$verified = verify_visiter();

if (!$verified) {
  exit('User verification failed.');
}

// ... everything else...

答案 2 :(得分:-1)

public function authenticateCalls($data = NULL, $bypassTimeCheck = FALSE)
{
    $da = array();
    foreach($data as $key => $val)
    {
        $da[$key] = $val; 
    }
    if(isset($da['hmac']))
    {
        unset($da['hmac']);
    }   
            
    ksort($da); 
    
    // Timestamp check; 1 hour tolerance
    if (!$bypassTimeCheck)
    {
        if (($da['timestamp'] - time() > 3600))
        {
            return false; 
        }
    }
    
    // HMAC Validation
    $queryString = http_build_query($da);
    $match = $data['hmac'];
    $calculated = hash_hmac('sha256', $queryString, $this->_API['API_SECRET']);
    
    return $calculated === $match;
}