shopify后台嵌入式App的鉴权

📅 2026/8/19 22:57:24
shopify后台嵌入式App的鉴权
官方提供这4种方式Authentication and authorization一.应用在 Shopify 管理后台 iframe 内运行属于嵌入式 AppEmbedded App使用第一种核心依据官方文档的说明用来校验 iframe 里 Nuxt 前端主动发起的 AJAX 请求Shopify 官方文档明确指出对于来自嵌入式应用的前端请求应该使用Session Tokens会话令牌来进行认证和授权。Session Token 就是 ID Token在 Shopify 的上下文中X-Shopify-Id-Token请求头传递的就是这个 Session Token。它是一个签名的 JWT用于向后端证明这个请求是从一个已认证的 Shopify 商店环境发起的。认证流程前端通过 Shopify App Bridge 提供的authenticatedFetch函数或getSessionToken方法获取此令牌并自动将其添加到后端 API 请求的Authorization头或X-Shopify-Id-Token头中。后端需要验证这个令牌的签名和内容是否有效。Webhooks delivery structure官方文档参考https://shopify.dev/docs/apps/build/authentication-authorization/session-tokens依赖firebase/php-jwtbash运行composer require firebase/php-jwt ^6.10兼容两种请求头你当前自定义头X-Shopify-Id-TokenShopify 标准规范头Authorization: Bearer xxx严格遵循 Shopify 官方 Session Token 校验规范所有必须校验的声明全部补齐适配 Webman包含鉴权服务类 Webman 中间件 控制器调用示例1. 完整服务类app/Service/ShopifySessionTokenService.phpphp运行?php namespace app\Service; use Firebase\JWT\ExpiredException; use Firebase\JWT\BeforeValidException; use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\JWT; use Firebase\JWT\Key; use Webman\Http\Request; class ShopifySessionTokenService { /** * 【修改为你自己的配置】 */ private string $clientId 你的App ClientID; private string $clientSecret shpss_; /** * 从Webman Request自动提取token并校验 * 兼容 Authorization: Bearer 与 X-Shopify-Id-Token 两种头 * param Request $request * return array payload * throws \Exception */ public function verifyByRequest(Request $request): array { $token null; // 优先读取标准 Authorization Bearer $authHeader $request-header(authorization, ); if (!empty($authHeader) str_starts_with(strtolower($authHeader), bearer )) { $token trim(substr($authHeader, 7)); } // 其次兼容你现在使用的自定义头 if (empty($token)) { $token $request-header(x-shopify-id-token, ); } if (empty($token)) { throw new \Exception(缺少Shopify Session Token); } return $this-verifyToken($token); } /** * 核心验签逻辑【严格 Shopify 官方安全规则】 * param string $jwtToken * return array * throws \Exception */ public function verifyToken(string $jwtToken): array { try { // HS256 签名校验 $decoded JWT::decode( $jwtToken, new Key($this-clientSecret, HS256) ); $payload (array)$decoded; } catch (SignatureInvalidException $_e) { throw new \Exception(Token签名非法请求疑似伪造); } catch (ExpiredException $_e) { throw new \Exception(Token已过期); } catch (BeforeValidException $_e) { throw new \Exception(Token尚未生效); } catch (\Throwable $e) { throw new \Exception(Token解析失败 . $e-getMessage()); } // 官方强制声明校验 开始 // 1. aud 必须等于 App Client ID if (!isset($payload[aud]) || $payload[aud] ! $this-clientId) { throw new \Exception(aud(受众)与当前应用不匹配); } // 2. iss 签发者校验 https://{shop}.myshopify.com/admin if (!isset($payload[iss])) { throw new \Exception(iss 签发者字段缺失); } $issUrlInfo parse_url($payload[iss]); if (!isset($issUrlInfo[host]) || !str_ends_with($issUrlInfo[host], .myshopify.com)) { throw new \Exception(iss 签发域名不合法); } // 3. dest 目标店铺域名校验 if (!isset($payload[dest])) { throw new \Exception(dest 目标店铺字段缺失); } $destUrlInfo parse_url($payload[dest]); // 4. 跨店铺防护iss店铺 和 dest店铺必须一致 $shopIssHost $issUrlInfo[host]; $shopDestHost $destUrlInfo[host] . .myshopify.com; if ($shopIssHost ! $shopDestHost) { throw new \Exception(iss与dest所属店铺不一致拒绝访问); } // 官方强制声明校验 结束 return $payload; } }2. Webman 全局鉴权中间件推荐使用新建app/middleware/ShopifyEmbeddedAuth.phpphp运行?php namespace app\middleware; use app\Service\ShopifySessionTokenService; use Webman\Http\Request; use Webman\Http\Response; class ShopifyEmbeddedAuth { public function process(Request $request, callable $next): Response { try { $service new ShopifySessionTokenService(); $payload $service-verifyByRequest($request); // 把解析后的payload挂载到request控制器直接读取 $request-shopify $payload; } catch (\Exception $e) { return json([ code 401, msg $e-getMessage(), data null ], 401); } return $next($request); } }注册中间件config/middleware.phpphp运行return [ // 针对shopify接口路由组启用 api [ app\middleware\ShopifyEmbeddedAuth::class, ], ];3. 控制器调用示例php运行?php namespace app\controller; use support\Request; class ShopApi { public function test(Request $request) { // 经过中间件校验后直接读取店铺信息 $shopData $request-shopify; return json([ code 0, msg 验签成功请求可信, shop_domain parse_url($shopData[dest])[host], shop_id $shopData[sub], payload $shopData ]); } }4. 测试调用示例两种头都支持方式 1你当前前端使用httpGET /shop-api/test HTTP/1.1 X-Shopify-Id-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxxx上面这种token 由shopify框架代入 但是有效期只有1min参考文档 前端使用拓展获取最新token代入About session tokens二.只用于 Shopify WebhookShopify 服务器主动 POST 推送数据到你的接口的x-shopify-hmac-sha256Webhook HMAC-SHA256 验签逻辑PHP/Webman官方文档Webhook HMAC 验证https://shopify.dev/docs/apps/build/events/verify-deliveries?php /** * Shopify Webhook HMAC验签 * param string $rawBody 请求原始body重点不要json_decode之后的字符串 * param string $headerHmac 请求头 X-Shopify-Hmac-SHA256 * param string $clientSecret shpss_xxx * return bool */ function verifyShopifyWebhook(string $rawBody, string $headerHmac, string $clientSecret): bool { $binaryHash hash_hmac(sha256, $rawBody, $clientSecret, true); $calcHmac base64_encode($binaryHash); // 时序安全对比杜绝时序攻击 return hash_equals($calcHmac, $headerHmac); } // Webman调用示例 $rawBody request()-getRawBody(); $hmacVal request()-header(x-shopify-hmac-sha256, ); $secret getenv(SHOPIFY_APP_CLIENT_SECRET); if (!verifyShopifyWebhook($rawBody, $hmacVal, $secret)) { return json([code 403, msg 非法Webhook请求], 403); } /** * Shopify Webhook HMAC校验 * param string $clientSecret App Secret * param string $rawBody HTTP原始请求body重点必须拿到未解析原始POST字符串不能用$_POST/json_decode之后的数据 * param string $hmacHeader 请求头 x-shopify-hmac-sha256 * return bool */ function verifyShopifyWebhookHmac(string $clientSecret, string $rawBody, string $hmacHeader): bool { $calculatedHmac base64_encode(hash_hmac(sha256, $rawBody, $clientSecret, true)); return hash_equals($calculatedHmac, $hmacHeader); }核心踩坑点必须读取原始 http body不要先 json_decode否则换行 / 空格变化导致签名失效结果是base64格式不是十六进制使用hash_equals防止时序攻击二、核心4 条官方条目精确对应你截图原文编号一一锁定plaintext1. Authenticate your app using session tokens. 使用会话令牌来验证你的应用程序。 → 【你的场景第一步】iframe嵌入式App前端鉴权x-shopify-id-tokenJWT 2. Authorize your app using a session token with token exchange. 通过令牌交换机制使用会话令牌来授权您的应用程序。 → 【你的场景第二步】拿session token去换Admin API access_token配套第1条使用 3. Authorize your standalone app with authorization code grant. 使用授权码来授权您的独立应用程序。 → 独立App、OAuth跳转安装、非iframe嵌入式老版嵌入式App安装流程也属于这套 4. Authenticate your app created in the Shopify admin with access tokens. 使用访问令牌来验证你在 Shopify 管理后台创建的应用程序。 →【Custom App 自定义应用】店铺后台手动创建自定义应用生成永久静态access_token