OAuth 代理
Better Auth 的 OAuth 代理插件
一个允许你代理 OAuth 请求的代理插件。在开发环境和预发布部署中非常有用,因为重定向 URL 无法提前确定并添加到 OAuth 提供商。
安装
在你的 auth 配置中添加此插件
import { betterAuth } from "better-auth"
import { oAuthProxy } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
oAuthProxy({
productionURL: "https://my-production-app.com",
secret: process.env.OAUTH_PROXY_SECRET,
}),
],
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID || "",
clientSecret: process.env.GITHUB_CLIENT_SECRET || "",
},
},
})将 OAUTH_PROXY_SECRET 在所有环境(production、preview、localhost)中设置为相同的值。
该插件会自动将 OAuth 请求通过你的生产服务器路由。
在你的 OAuth 提供商处添加重定向 URL
在你的 OAuth 提供商的开发者控制台(例如 GitHub、Google)中,使用你的生产域名注册回调 URL。例如:
https://my-production-app.com/api/auth/callback/github只需注册生产环境的回调 URL。插件会自动处理将预览和开发环境的 OAuth 请求路由到生产环境。
添加可信来源
由于预览和开发服务器会通过生产环境重定向,你需要将它们添加为 trustedOrigins:
export const auth = betterAuth({
// ...其他配置
trustedOrigins: [
"http://localhost:3000",
"https://my-app-*-preview.example.com",
],
})重要:需要共享密钥
所有环境(production、preview、localhost)都必须使用相同的加密密钥进行通信。在插件选项中配置一个专用的 secret:
oAuthProxy({
productionURL: "https://my-production-app.com",
secret: process.env.OAUTH_PROXY_SECRET, // 所有环境都使用相同的值
})如果你没有配置共享的 secret,插件会回退到 BETTER_AUTH_SECRET。由于生产环境和预览环境通常会使用不同的主密钥(这在安全上是正确的做法),OAuth 流程将会失败,并报出 state_mismatch 错误。
工作原理
该插件允许你在多个环境中使用单一的 OAuth 客户端(注册于你的生产 URL),例如预览部署或本地开发。
- 预览服务器发起 OAuth,重定向至包含生产环境重定向 URI 的 OAuth 提供商
- OAuth 提供商回调至生产服务器
- 生产服务器用 code 换取令牌并获取用户信息
- 生产服务器加密用户信息并重定向回预览服务器(生产环境无数据库写入)
- 预览服务器解密用户信息,在自己的数据库创建用户/会话,并设置会话 Cookie
import { authClient } from "@/lib/auth-client"
await authClient.signIn.social({
provider: "github",
callbackURL: "/dashboard"
})加密的用户信息通过 URL 查询参数传递,只有共享相同密钥的服务器能解密。这也允许预览环境使用与生产环境不同的数据库(如果需要)。
此插件仅适用于开发和预发布环境。如果 baseURL 和 productionURL 相同,插件不会代理请求。
选项
productionURL:你的生产服务器的 URL。如果此值与 auth 配置中的 baseURL 相匹配,请求将不会被代理。默认为 BETTER_AUTH_URL 环境变量。
currentURL:插件会自动确定应用当前的 URL。它会优先检查请求的 URL、常见托管服务提供商的特定环境变量,最后回退到 auth 配置中的 baseURL。只有在环境中无法正确推断 URL 时才需要手动设置。
maxAge:加密载荷的最大有效期(秒)。超过此时间的载荷将被拒绝以防止重放攻击。保持较短的值(例如 30-60 秒)可以最大程度减少潜在重放攻击的窗口,同时仍允许正常的 OAuth 流程。默认为 60 秒。
secret:在 OAuth 代理流程中用于加密和解密数据的专用密钥。设置后,将使用它而不是全局的 BETTER_AUTH_SECRET,从而在跨环境共享密钥时限制影响范围——泄露的代理密钥无法伪造会话或解密受主密钥保护的其他数据。参与代理流程的所有环境必须共享相同的 secret 值。
故障排查
state_mismatch 或“State not persisted correctly”错误
当生产环境和预览环境使用不同的密钥,并且插件选项中没有配置共享的 secret 时,通常会出现此错误。
发生了什么:
- 预览环境使用自己的密钥加密 OAuth state
- OAuth 提供商重定向到生产环境
- 生产环境尝试使用不同的密钥解密 → 失败
- 常规 OAuth 回调运行失败,因为生产环境上不存在 state cookie
解决方案: 在所有环境的 oAuthProxy 选项中配置一个共享的 secret:
oAuthProxy({
productionURL: "https://my-production-app.com",
secret: process.env.OAUTH_PROXY_SECRET,
})确保 OAUTH_PROXY_SECRET 在 production、preview 和 localhost 上的值相同。
建议使用专用的代理密钥(而不是共享 BETTER_AUTH_SECRET),这样更安全。如果代理密钥泄露,攻击者无法伪造会话或访问其他加密数据——他们最多只能在较短的 maxAge 窗口内劫持 OAuth 流程。
OAuth 在生产环境正常,但在 preview/localhost 上失败
请确保以下所有项都已配置:
- 已配置共享密钥(见上文)
- 可信来源包含你的 preview/localhost URL
- 已在 OAuth 提供商处注册生产回调 URL(例如
https://production.com/api/auth/callback/github)