shopify

Shopify Online Token 和 Offline Token 怎么选

Shopify online 和 offline access token 分别适合哪些请求?从员工权限、后台任务和 token 刷新说起。

约二千七百字·读约八分钟 · English

Shopify Online Token 和 Offline Token 怎么选

Shopify 把 Admin API access token 分成 online 和 offline,很容易让人按字面理解:员工在页面里操作就用 online,后台运行就用 offline。后半句通常没问题,前半句却不一定。一个页面请求也可以用 offline token,关键在于你是否需要 Shopify 按当前员工的权限处理它。

比如应用里有个“给订单加备注”的按钮。如果只有具备相应订单权限的员工才能操作,而且希望由 Shopify 来检查这层权限,就用 online token。如果这个按钮本来就是应用提供的店铺级功能,也可以用 offline token;只是这时员工能否点按钮,得由应用自己管。Token 类型不会替你补上产品里的权限规则。

换成另一个场景就容易看出区别。订单同步服务收到 Webhook 后,把订单送进队列,几分钟后由 worker 调 Admin API 补查详情。这条链路里可能从头到尾都没有员工打开应用。如果最初把页面请求拿到的 online token 顺手交给队列,开发环境里也许能跑通,等员工退出或 token 到期,队列才开始报认证错误。此处应该使用按店铺保存的 offline token。

一个应用同时有客服页面和订单同步任务并不稀奇,两类 token 也可以同时存在。麻烦通常出在把它们都叫 shop.accessToken,后来获取到的凭证覆盖了先前那一条。

Shopify online 与 offline token 的选择流程图

Online token 跟员工绑定

Online token 的访问范围同时受应用获批的 scopes 和当前员工权限限制。遇到权限错误,除了看应用有没有申请对应 scope,还要看这位员工在店铺里有没有相应权限。店主能调用成功,不代表客服也能。

这里说的员工权限是 Shopify 已经认识的权限,不等于应用自己的所有业务规则。比如你在客服系统里规定“只能修改分配给自己的工单”,这类分配关系如果只存在于你的数据库,就仍要由你的后端检查。Online token 不会凭空知道哪张工单归谁。

它也不适合交给后台任务长期使用。Online token 最长有效 24 小时,员工退出 Shopify Admin 后也会失效,而且没有 refresh token。嵌入式应用可以在员工会话仍有效时,用新的 ID token 再做 token exchange;独立应用则要走自己的授权流程重新取得。定时任务若依赖它,员工一退出,任务就可能停在认证这一步。

需要缓存时,至少按「店铺 + 员工」区分,不能只给每家店留一条 online token。Shopify 响应里的 associated_user.id 是员工标识,associated_user_scope 则可用来查看这次取得的员工权限范围。独立应用通过授权码流程申请 online token,要在授权地址加上 grant_options[]=per-user。

排查错误时也别把“没权限”和“token 过期”混成一件事。Shopify 的 GraphQL Admin API 在 token 有效、但员工无权执行操作时,会在 GraphQL 错误的 extensions.code 里给出 ACCESS_DENIED;token 过期则会得到 401 Unauthorized。前者应检查 app scope 和员工权限,后者要检查是否到期或被撤销,再决定怎样重新取得凭证。若把两种情况都写成“请重新登录”,员工登录多少次也解决不了 scope 不足。

嵌入式应用怎样拿到 access token

在嵌入式应用里,浏览器从 App Bridge 拿到的是 ID token。它有效期很短,适合带给自己的后端证明当前会话。后端验证后,再用它向 Shopify 发起 token exchange,换到真正可调用 Admin API 的 access token。请求 online 还是 offline,由 token exchange 的 requested_token_type 决定;请求可过期 offline token 时还要传 expiring=1。

这一步如果使用 Shopify CLI 生成的官方应用模板,先检查模板提供的认证方法,不必手写一套相同流程。自己实现时,ID token 要在需要交换时重新获取,不能把浏览器第一次拿到的那枚一直缓存着用。独立应用的授权码流程是另一条路径;它请求 online token 时使用的 grant_options[]=per-user,不要照搬进嵌入式应用的 token exchange 参数里。

Offline token 给不依赖员工会话的任务用

Webhook 到来时,不能指望恰好有员工登录;夜间同步订单、处理队列也一样。这些请求用 offline token。它代表应用在这家店获得的授权,不跟某位员工的会话一起结束。Shopify 默认提供的也是 offline access。

“Offline”说的是它不依赖员工的网页登录会话,不是说所有 offline token 都没有到期时间。这个区别在旧项目里尤其容易被忽略:早期集成只拿过一次 token,数据库里也只有一个字符串字段;代码跑了几年,大家就默认它永远有效。迁移到可过期 token 后,原来的字段已经装不下刷新所需的信息。

公共应用的规则也在变化。Shopify 已开始要求公共应用改用可过期的 offline token:

  • 2026 年 4 月 1 日起,新建且调用 Admin API 的公共应用适用。
  • 2027 年 1 月 1 日起,所有调用 Admin API 的公共应用都适用,包括此前创建的应用。

变更公告列出的范围包括 GraphQL 和 REST Admin API。自定义应用及商家自行创建的应用不受这项要求约束。即使用的是无到期时间的 offline token,应用卸载或凭证撤销后也不能继续用。

可过期 offline access token 当前的有效期是 1 小时。实现时用 Shopify 返回的 expires_in 计算到期时间,别把这个时长写成常量。响应里还会给 refresh_token_expires_in;这两个到期时间都值得存下来。

Refresh token 签发时的有效期目前是 90 天,但每次刷新后的剩余时间可能变化。实际存储时以响应值计算时间,不要假设“每次刷新都能再用 90 天”。后台任务应在 access token 到期前留出刷新余量;如果等到 API 已经返回 401 才开始处理,恰好在跑的 Webhook 或队列任务就得先承受一次失败。Refresh token 自己到期后也不能靠它继续刷新,需要通过相应的授权路径重新取得凭证。

刷新后会得到新的 access token 和 refresh token。数据库更新时把两者连同到期时间一起写入,否则下一次刷新可能拿到旧的 refresh token。使用 Shopify 官方应用模板的项目,先看看模板已经帮你处理了多少;自己接 OAuth 和 token exchange 时,才需要完整实现这段逻辑。

存储时可以先把 online 和 offline 分成两种记录,而不是共用一个 token 字段:

offline: shop, access_token, access_token_expires_at,
         refresh_token, refresh_token_expires_at

online:  shop, user_id, access_token, access_token_expires_at

这只是表达需要区分的字段,不要求一定建两张表。无论怎么存,凭证都只留在服务端,业务日志里也别打印它们。Online token 即使还没到记录的到期时间,也可能因为员工退出而失效;offline token 同样可能因应用卸载或凭证撤销而失效。到期时间是提前刷新的依据,不是“在此之前必定可用”的保证。

可过期 offline token 的换取、保存和刷新时序图

还有一个容易漏的并发问题:同一店铺的两个 worker 可能同时发现 token 将过期,然后各自刷新、各自写库。按店铺串行化刷新,让其他任务复用更新后的凭证,可以避免两个刷新结果互相覆盖。旧 refresh token 并非第一次使用后就立刻失效,Shopify 给它留了有限的可用窗口;但刷新与重新获取 offline token 如果同时发生,其中一条路径拿到的凭证仍可能被另一条路径取代。

在多 worker 部署里,做法可以是:拿到按店铺划分的锁后,再读一次凭证;若别的 worker 已经刷新,就直接用新的记录;否则调用刷新接口,并把 access token、refresh token 和两个到期时间作为同一次更新写入。锁或队列也应覆盖“重新通过 token exchange 获取 offline token”这条路径,别只保护定时刷新。Shopify 文档明确提醒,同一店铺一边刷新、一边重新获取 token,会让两次请求的结果互相影响。

这里还有一个细节:Shopify 并非在你提交旧 refresh token 的瞬间就把它作废。旧 token 会保留到新的 refresh token 被使用、应用重新取得 token,或达到 Shopify 规定的时间上限等条件之一。这个窗口是为了允许正常轮换,不是让多个 worker 长期共用旧 token 的理由。数据库里仍应以最新返回的那一组凭证为准。

ID token 不是 Admin API access token

嵌入式应用从 App Bridge 拿到的 ID token,是前端向自己后端证明 Shopify 会话的凭证。后端验证后,可以用它向 Shopify 换取 access token。它不能直接放进 X-Shopify-Access-Token 调 Admin API。

也别用 shpat_ 前缀判断 online 还是 offline,两类 access token 都可能以它开头。存凭证时把类型、店铺、员工(如果是 online)和到期时间明确记下来。这样以后给页面加员工权限,或给后台加定时任务,不会让新拿到的 token 覆盖原来那一条。

如果接手的是一个已有项目,可以先沿着三条请求看代码:员工点击页面按钮后用的哪条凭证,Webhook 或队列运行时从哪里读凭证,token 将到期时由谁负责刷新。把这三条链路画清楚,比先改 OAuth 参数更容易找到“白天能用、夜里失败”的原因。尤其要检查缓存键和数据库唯一键,确认 online token 没有按店铺覆盖 offline token,也没有让不同员工共用同一条记录。

官方资料

Mttao

Mttao GitHub ↗

探索技术与生活的智慧

相关文章

/ 评论