EN
Shopify 知识库 · 指南

Shopify OAuth 回调指南:URL、参数与验证顺序

区分 Shopify App 的 application_url、redirect_urls 与授权请求 redirect_uri,说明回调参数、state、HMAC、自定义查询参数和多 App 分流的安全处理顺序。

application_url、redirect_urls 和 OAuth 请求里的 redirect_uri 经常被混在一起。它们都包含 URL,但职责不同:一个是应用入口,一个是允许回调的配置,一个是本次授权实际使用的地址。

本文针对运行在 Shopify Admin 之外、使用 authorization code grant 的 standalone 或 API-only App。使用 Shopify CLI 模板的嵌入式 App 通常采用 Shopify-managed installation 与 token exchange,不应为了套用本文而自行增加旧式安装回调。认证方式总览

三个 URL 的职责

名称用途谁使用
application_url商家打开 App 时进入的首页Shopify Admin 与 App 导航
redirect_urlsOAuth 回调地址白名单App 配置与 Shopify 授权服务
redirect_uri本次授权完成后实际返回的地址应用生成的授权请求

Shopify 要求授权请求中的 redirect_uri 与已经配置的回调地址完全一致,不是前缀匹配。独立应用认证流程

redirect_urls 不是登录成功后可以跳往的任意业务页面列表。通常先回到固定认证接口,验证并建立 Session 后,再由应用跳转到最终页面。

授权与回调流程

sequenceDiagram
  participant M as 商家浏览器
  participant A as App 后端
  participant S as Shopify
  M->>A: 开始安装或授权
  A->>A: 生成并保存随机 state
  A-->>M: 重定向到 Shopify 授权页
  M->>S: 同意权限
  S-->>A: 回调 redirect_uri
  A->>A: 校验 shop、state、HMAC 与时间
  A->>S: 使用 code 换取 access token
  S-->>A: 返回 token
  A-->>M: 进入应用界面

Shopify 当前 authorization code grant 文档说明回调会带上 code、hmac、shop、state 和 timestamp,并要求授权请求里的 redirect_uri 与 Dev Dashboard 或配置 TOML 中登记的某个回调地址完全一致。实现不应因为其他入口还出现了 host 等参数,就把它当成此回调契约保证存在的字段;以正在使用的官方认证方式和框架契约为准。

state 保存动态上下文

授权开始时生成随机 nonce,把它与待恢复的服务端上下文关联:

state → {
  expectedShop,
  returnPath,
  organizationId,
  appKey,
  expiresAt
}

回调时先比较 state,并在使用后失效。这样既防止 CSRF,也无需把组织 ID、内部返回地址或权限信息直接放进 redirect_uri。

不要把未经签名的动态上下文直接信任为 callback query。即使使用签名 Cookie 或加密 state,也要限制有效期和一次性使用。

回调地址能否带固定参数

OAuth 2.0 允许 redirect URI 自身包含 query,授权服务器追加回调参数时需要保留它。RFC 6749 §3.1.2

例如:

[auth]
redirect_urls = [
  "https://apps.example.com/auth/callback?surface=console"
]

那么发起授权时的 redirect_uri 也必须使用同一个完整地址。固定参数不要与 Shopify 回调字段重名,验证 HMAC 时也不能随意丢弃 callback query 中的字段。

固定参数适合区分稳定入口,不适合携带每次请求变化的组织、用户或最终返回页面。动态信息应通过服务端 state 关联。

推荐的验证顺序

  1. 解析请求,但暂不使用 code。
  2. 验证 shop 的格式是否是允许的 Shopify 店铺域名,正则两端都要加锚点,例如 ^[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com$。
  3. 读取并一次性消费服务端保存的 state,核对店铺、App 与有效期。
  4. 验证 HMAC:从回调参数中取出 hmac 本身,其余参数按字母序排列,用该 App 的 client secret 计算 HMAC-SHA256,并用常量时间比较结果。
  5. 检查时间戳或请求时效,拒绝明显重放。
  6. 使用同一 App 的 client ID、secret 和同一个 redirect_uri 交换 token。
  7. 持久化安装记录和 Session。
  8. 只跳转到事先允许的站内路径。

不要在 HMAC 和 state 通过之前建立店铺绑定,也不要把授权码或 access token 返回到浏览器可读的业务 URL。

多 App 共用域名

两个 Shopify App 可以共享域名,但最好使用独立回调路径:

/shopify/public/auth/callback
/shopify/custom/auth/callback

路径先确定 App 身份,然后服务器选择对应 secret。两个 App 即使登记相同回调地址,也必须分别维护 client ID、secret、scope、Session 和 access token;token 不属于域名,而属于具体 App 与店铺的安装关系。

完整的多 App 后端隔离见两个 Shopify App 共用一个后端。

排查清单

  • 授权请求的 redirect_uri 是否与配置中的完整地址一致。
  • 当前执行的是哪一个 App 配置,是否用了另一个环境的 client ID。
  • state 是否存在、未过期、只使用一次,并对应当前店铺和 App。
  • HMAC 是否基于完整且规范化的回调参数验证。
  • callback 是否错误依赖某个非当前认证契约保证的参数。
  • token 交换是否使用了同一个 redirect_uri 和正确 secret。
  • 最终业务跳转是否限制为站内允许路径。

回调 URL 只是入口。OAuth 的安全性来自入口、服务端 state、签名验证与 App 凭据始终属于同一条授权链。