如何在 Next.js、Expo 与 Tauri 之间共享认证和支付?
取决于能否保持同一身份模型与同一权益存储。Web 用 HttpOnly Cookie 会话;Expo 在 Deep Link 的 OAuth 回调后把 Bearer Token 存入 expo-secure-store;Tauri 在 Webview 内直接复用 Web 会话——三者由同一个 Better Auth 实例校验。支付同理:8 家 Web 网关与 RevenueCat 移动内购汇入同一个幂等 webhook 账本,写入每个端面都能读取的工作区级权益。
跨端 SaaS 断裂的两条缝
问任何一个把移动应用焊接到 Web SaaS 上的团队真正疼在哪,答案永远不是 UI。是身份和钱:在浏览器里干净回跳的 OAuth 流程,必须通过 Deep Link 跳回应用内;而在 Web 上付过款的客户,期望权益立刻出现在手机里。
团队通常在上线过两个半成品之后才发现这一点:在 WKWebView 里悄悄失效的 Cookie 逻辑,以及与 Web 数据库里 Stripe 订阅互相打架的 RevenueCat 权益。修复是架构层面的,而且是同一个修复用两次——一个身份模型,一个权益存储。
一个 Better Auth 实例,三种会话传输
supastack 在 @supastack/auth 中保留单一的 Better Auth 配置,为每个端面提供不同入口。两个 Web 应用(Next.js 16 与 TanStack Start)使用 HttpOnly、SameSite=Lax 的 Cookie 会话——默认防 CSRF 且与 Server Components 天然契合。Expo 应用在 Deep Link 回调结束时把 OAuth 或魔法链接的完成态换成 Bearer Token,持久化到 expo-secure-store(iOS Keychain / Android Keystore),随后以 Authorization 头发送,由同一个认证中间件校验。
Tauri 的桌面壳是一个指向 Web 应用的单 Webview,因此直接继承 Web 会话——没有第三条认证流程要维护。移动端认证指南与 Deep Link 指南 记载了回调契约;我们的 Web + 移动 Monorepo 指南演示了共享客户端代码。
// packages/auth/src/client.ts — one client, two storages
import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo/client";
export function createCrossPlatformAuthClient(baseURL: string, storage?: any) {
return createAuthClient({
baseURL,
plugins: storage ? [expoClient({ scheme: "myapp", storage })] : [],
});
}
// Web: cookie session. Mobile: bearer token in SecureStore.
// Desktop: the web app's own session inside the Tauri webview.八家支付网关,同一份契约
每一家 Web 支付商——Stripe、Lemon Squeezy、Polar、Creem、Dodo、Paddle、Whop 与 PayPal——都接入同一个 @supastack/billing 契约,每一条签名 webhook 都先落进幂等的 webhook_event 账本,才被允许触碰权益。服务商重试与乱序送达在数据库边界变成无害空操作,而不是客服工单。
移动端内购通过 RevenueCat 汇入同一本账:商店收据在服务端校验,权益写到工作区而非设备上——在 iOS 付过款的用户,在 Web 与桌面端立刻同样是已付费。RevenueCat 指南讲 App Store / Play 的接线;幂等计费 webhook 解释为什么账本才是承重件。
让同步变得无聊的权益模型
这件事之所以能维持无聊,是因为权益放在工作区上、由服务端解析。没有任何客户端存储「已付费」——Web 问会话、移动端问 API、桌面端问 Web 应用——三者拿到同一个答案,因为只有一份记录可答。
新手机上的恢复购买、Web 上的 Stripe 续费、后台里新增的 Agency 席位——每一笔都是写进同一些权益行,每个端面下次询问时读到它。没有同步引擎可建,因为根本没有东西需要同步。
| 端面 | 会话传输 | 支付路径 | 权益读取自 |
|---|---|---|---|
| Next.js 16 Web | HttpOnly Cookie | 8 家网关任一 → webhook 账本 | 共享 Postgres 权益行 |
| TanStack Start Web | HttpOnly Cookie | 同一批适配器,同一本账 | 共享 Postgres 权益行 |
| Expo iOS / Android | expo-secure-store 中的 Bearer Token | RevenueCat 内购 → 服务端校验收据 | 同一批行(经类型化 API) |
| Tauri 2 桌面端 | Webview 内的 Web 会话 | 壳内直接走 Web 收银台 | 同一批行(经 Web 应用) |
| MV3 插件 | 工作区 API 密钥 | —(读取工作区数据) | 同一批行(经 /api/v1) |
你省下的开发量
按业余晚上的时间折算,这就是「地基」与「功能清单」的差别:Deep Link 登录回跳、SecureStore 令牌持久化、八个带重放安全的 webhook 集成、App Store 收据校验,以及一个跨设备永不自相矛盾的权益模型。每一项都已交付、已测试(693+ 测试免凭证可跑)、已写进文档。
$299 的 Full Bundle 在同一个仓库里包含全部端面——席位与外包档位见 定价页。
常见问题:跨平台共享认证与支付
团队在押注统一技术栈之前常问的问题:
- 移动端用户要脱离 Web 单独登录吗?, 不用。OAuth 或魔法链接流程通过 Deep Link 回到应用并签发同一个会话;签发方是同一个 Better Auth 实例,所以 Web 与移动端对「你是谁」答案一致。
- 有人在 iOS 上付款却用 Web 端怎么办?, RevenueCat 在服务端送达这笔购买;权益写进 Web 读取的同一批工作区行,Web 端下一次请求就显示已付费。
- 能先只做 Web、之后再上应用吗?, 能——认证与计费都在共享包里,移动端和桌面端应用在你开启这些端面时直接继承它们。
- Tauri 应用需要自己的收银台吗?, 不需要——桌面壳在 Webview 内复用 Web 应用的会话与收银台,没有第二个支付端面需要设防。