Shopify OAuth 回调指南:URL、参数与验证顺序
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_urls | OAuth 回调地址白名单 | 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 关联。
推荐的验证顺序
- 解析请求,但暂不使用
code。 - 验证
shop的格式是否是允许的 Shopify 店铺域名,正则两端都要加锚点,例如^[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com$。 - 读取并一次性消费服务端保存的
state,核对店铺、App 与有效期。 - 验证 HMAC:从回调参数中取出
hmac本身,其余参数按字母序排列,用该 App 的 client secret 计算 HMAC-SHA256,并用常量时间比较结果。 - 检查时间戳或请求时效,拒绝明显重放。
- 使用同一 App 的 client ID、secret 和同一个
redirect_uri交换 token。 - 持久化安装记录和 Session。
- 只跳转到事先允许的站内路径。
不要在 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 凭据始终属于同一条授权链。