EN
Shopify 知识库 · 指南

两个 Shopify App 共用一个后端:隔离身份而不是复制业务

说明 Public、Custom 或不同环境的 Shopify App 如何共用业务服务,同时隔离凭据、授权、Session、Webhook、计费与删除事件,避免同一店铺的安装记录和令牌互相覆盖。

两个 Shopify App 可以进入同一个管理后台,也可以复用同一套业务服务和数据库。真正需要隔离的不是页面代码,而是每个 App 与 Shopify 之间的安全边界。

如果后端只按店铺域名保存状态,第二个 App 安装到同一家店时,可能读取第一个 App 的 access token、覆盖其 Session,或用错误的 secret 验证 Webhook。共用后端的基本原则是:业务可以按店铺共享,平台身份必须按 App 分开。

先分清三种身份

身份回答的问题典型来源
App 身份当前请求属于哪个 Shopify App固定入口、部署配置或已验证的 token
店铺身份当前操作属于哪家店已验证请求中的 shop,或数据库中的店铺记录
安装代次属于这个 App 的哪一次安装安装记录、Session 版本或等价的生命周期标识

App 身份不能由浏览器随意提交的 app=public 决定。更稳妥的方式是让固定路径或独立部署先确定 App,再用该 App 的凭据验证请求。

Public App ── /shopify/public/* ──┐
                                  ├─ 共享业务服务、管理界面和数据库
Custom App ── /shopify/custom/* ──┘

安装代次也不能省略。某个 App 卸载后重装,迟到的刷新请求或 Webhook 可能仍属于上一次安装。只保存 appKey,无法判断这个事件是否仍有权修改当前记录。

需要区分 App 的位置

授权入口与回调

每个 App 拥有自己的 client ID 和 secret。使用 authorization code grant 时,授权入口先确定 App,再生成授权 URL;回调路径继续携带这一受信任的上下文,并使用对应 secret 验证 HMAC、交换 token。使用 Shopify-managed installation 的嵌入式 App 则应按当前模板通过 App Bridge ID token 与 token exchange 确定 App 和店铺,不能把两种认证流程混成一套。ID token 只用于认证、签发后一分钟过期,必须由后端验证后换取 access token,不能直接拿去调用 Admin API。ID tokens

两个 App 可以使用同一域名,但建议使用不同路径:

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

这样在解析回调参数之前,服务器已经知道应该选择哪套配置。application_url、redirect_urls 与回调校验的具体关系见OAuth 回调指南。

Session 的读、写、删与刷新

Session 不能只使用 offline_<shop> 作为全局唯一键。至少需要把 App 身份加入存储边界,例如使用 (appKey, sessionId),并在读取时再次核对归属。

只在写入 Session 时检查 App 不够。旧 App 的 Session 仍然有效时,读取路径可能先命中它,绕过后续的写入检查。删除和刷新同样需要限定 App 与安装代次:刷新只能更新仍属于当前安装的记录,不能在卸载后重新建立归属。

Admin API client

业务代码不应到处判断 Public 或 Custom。更好的接口是:业务层只提供店铺,平台层查询有效安装,再返回使用正确 App 凭据和 token 的 Admin client。

业务动作
  → 按店铺查有效安装
  → 确定 App 与安装代次
  → 载入对应 Session
  → 创建 Admin API client

这能把凭据选择集中在少数模块,避免同步、上传、后台任务和 MCP 等调用方各自实现一套判断。

Webhook 与删除事件

Webhook 必须先用接收它的 App secret 验签,再判断事件是否属于当前安装。app/uninstalled 应只释放对应 App 的 Session 与归属,不能直接删除该店铺的全部平台数据。

隐私删除需要单独设计。Shopify 说明,shop/redact 在店主卸载应用 48 小时后发送。隐私合规文档 如果店铺在这段时间已经由另一个 App 接管,旧 App 的删除事件不能未经判断就清除新安装仍在使用的数据。

这里不能简单地得出“共享数据永远不删”。应用仍需履行适用的数据删除义务。实现需要记录数据来源、保留依据和安装阶段,让删除范围既不会误伤新安装,也不会因为共用数据库而逃避合规处理。

计费策略

共用业务功能不代表计费方式一致。Public App 可能使用 Shopify App Pricing,Custom distribution App 可能由合同或其他授权决定权限。计费门禁应当由安装记录选择策略,而不是在每个业务接口里散落 if public。

店铺认领模型

如果产品规则是“一家店同一时间只能使用其中一个 App”,可以为店铺保存当前认领:

ShopInstallation
- shopId
- appKey
- installationGeneration
- status
- installedAt
- uninstalledAt

建立认领时需要比较两项:数据库里的现有归属,以及当前已经通过验证的 App 身份。首次安装的申请方不能从数据库推导出来,因为数据库此时还没有记录;它必须来自当前授权实例。

如果同一店铺允许同时安装两个 App,就不要设置单一认领字段,而应使用 (appKey, shopId) 作为安装唯一边界。选择哪种模型取决于产品规则,不应由 Session 表现偶然决定。

迁移与回滚

把单 App 系统改成多 App 系统时,最危险的阶段是旧代码与新数据模型同时写入。一次提前回填无法覆盖回填之后、正式切换之前发生的新安装和卸载。

可靠的发布顺序通常包括:

  1. 先上线能读写新归属字段、同时兼容旧数据的版本。
  2. 回填现有安装,并列出无法确定来源的记录。
  3. 停止旧写入进程,做最终对账。
  4. 再开放第二个 App 的安装入口。
  5. 验证安装、刷新、卸载、迟到 Webhook 和重新安装。

首次出现第二个 App 的有效安装后,回滚到完全不理解 App 隔离的旧版本通常不再安全。此后的回滚目标必须仍然保留身份隔离能力,或者先执行明确的数据恢复与店铺迁回流程。

验收场景

  • 同一店铺安装 App A 后,App B 的授权按产品规则被拒绝或建立独立安装。
  • App A 的有效 Session 不能被 App B 的请求读取。
  • App A 刷新 token 与卸载交错时,不会在卸载后恢复旧归属。
  • App A 卸载后 App B 接管,迟到的 A Webhook 不会删除 B 的 Session。
  • 每个 Webhook 都使用对应 App secret 验签。
  • 后台任务只通过统一的“按店取 Admin client”入口调用 Shopify。
  • 切换期间产生的新安装和卸载都进入最终对账。

共用后端的目标不是让两个 App 看起来像一个 App,而是让它们在安全边界内共享同一套业务能力。