Convex与Better Auth集成:构建实时安全的现代Web认证系统
1. 项目概述:为什么选择 Convex + Better Auth?
在构建现代 Web 应用时,身份认证(Authentication)和授权(Authorization)是两块绕不开的基石。然而,自己从零搭建一套安全、健壮且功能完整的认证系统,其复杂度和潜在风险远超大多数开发者的想象。你需要处理密码哈希、会话管理、OAuth 流程、多因素认证(MFA)、邮件发送、数据库安全等一系列问题,任何一个环节的疏忽都可能导致严重的安全漏洞。
这正是像 Better Auth 这样的库存在的意义。它提供了一个开箱即用、高度可配置且符合最佳安全实践的身份认证解决方案。而 Convex,作为一个实时后端平台,以其声明式数据模型、实时查询和极简的服务器函数(Functions)著称,极大地简化了全栈开发。将 Better Auth 与 Convex 结合,意味着你可以用最少的代码,获得一个功能强大、实时同步且安全可靠的身份认证系统。这不仅仅是“能用”,而是“好用”和“敢用”。对于个人项目、初创公司或需要快速迭代的产品来说,这个组合能让你将精力集中在核心业务逻辑上,而不是反复造轮子。
2. 核心思路与架构设计
2.1 技术栈选型解析
这个组合的核心思路是“各司其职,无缝集成”。Better Auth 专注于处理所有与认证相关的复杂逻辑,而 Convex 则作为应用的数据层和实时引擎。
Better Auth 的角色: 它是一个框架无关的认证库。这意味着它不强制绑定任何特定的前端框架(如 React, Vue)或后端运行时(如 Node.js, Bun)。它提供了一套标准的 API 和适配器(Adapters),让你可以将其“插入”到你的应用架构中。它的核心职责包括:
- 会话管理:安全地创建、验证和销毁会话。
- 凭证验证:处理邮箱/密码、OAuth 流程、WebAuthn 等。
- 安全实践:自动处理密码加盐哈希、CSRF 防护、安全 Cookie 设置等。
- 扩展功能:内置 2FA、邮件验证、账户管理等功能。
Convex 的角色: Convex 在这里扮演了两个关键角色:
- 数据存储:Better Auth 需要一个地方来存储用户、会话、账户、验证令牌等数据。Convex 的数据库(基于 FoundationDB)是一个高性能、强一致性的选择。我们将通过 Better Auth 的数据库适配器,让 Better Auth 直接读写 Convex 的数据库表。
- 服务端环境:Better Auth 的部分逻辑(如 OAuth 回调处理、邮件发送触发)需要在服务端安全地执行。Convex 的服务器函数(
mutation,action)提供了完美的无服务器执行环境。
数据流设计: 典型的认证流程如下:
- 用户在 React/Next.js 前端点击“登录”。
- 前端调用 Better Auth 客户端 SDK 提供的方法(如
signIn)。 - Better Auth 客户端 SDK 会根据配置,将请求发送到对应的服务端端点。在 Convex 集成中,这个端点通常是一个 Convex HTTP Action。
- 该 Convex Action 内部调用 Better Auth 的服务端 API 进行核心认证逻辑处理。
- Better Auth 在处理过程中,会通过我们配置的 Convex 数据库适配器,对 Convex 数据库进行增删改查。
- 认证结果(成功或失败)通过 Convex Action 返回给前端。
- 前端根据结果更新 UI,并且后续的会话状态可以通过 Better Auth 的客户端钩子(如
useSession)或 Convex 的实时查询来获取和同步。
这种设计确保了认证逻辑的集中和安全,同时利用了 Convex 的实时特性,让登录状态的变化可以即时反映在所有客户端。
2.2 环境与工具准备
在开始编码之前,确保你的环境已经就绪。这里假设你正在启动一个 Next.js 项目(App Router),这是目前最流行的全栈 React 框架之一,也与该组合的官方示例高度契合。
1. 创建 Convex 项目:首先,你需要在 Convex 官网 注册并创建一个新项目。安装 Convex CLI 并初始化你的项目。
npm install -g convex # 在你的项目根目录下运行 convex init运行convex init会引导你登录、选择或创建项目,并在本地生成convex/目录以及convex.json配置文件。
2. 初始化 Next.js 项目(如果尚未创建):
npx create-next-app@latest my-app --typescript --tailwind --app cd my-app3. 安装核心依赖:在你的 Next.js 项目根目录下,安装以下包:
npm install better-auth convex npm install @convex-dev/react @convex-dev/nextjsbetter-auth: Better Auth 的核心库。convex: Convex 的 JavaScript/TypeScript 客户端。@convex-dev/react: 在 React 组件中使用 Convex 的钩子(如useQuery)。@convex-dev/nextjs: Next.js 专用的 Convex 工具,用于服务端集成。
4. 安装开发依赖(用于适配器):Better Auth 需要特定的数据库适配器来连接 Convex。由于 Convex 使用自定义的数据库驱动,你需要安装better-auth的 Convex 适配器包。根据 Better Auth 文档,这个包通常是@better-auth/convex。同时,Convex 的数据库操作需要其特定的类型生成器。
npm install @better-auth/convex npm install -D convex@latest确保convex作为开发依赖也安装最新版,以使用npx convex codegen命令。
注意:依赖的版本兼容性至关重要。在开始前,务必查阅 Better Auth with Convex 官方指南 以获取确切的、经过测试的版本号。不同版本的 Better Auth 和 Convex 可能在 API 上存在细微差别。
3. 核心配置与集成实现
3.1 配置 Better Auth 服务端
Better Auth 的核心配置在一个服务端文件中完成。在 Next.js 的 App Router 中,我们通常将其放在app/api/auth/[...all]/route.ts这样的“捕获所有”API 路由中,或者创建一个独立的服务端配置文件。这里我们采用一种更清晰的方式:在 Convex 的上下文中配置。
首先,在convex/目录下创建一个auth.ts文件。这个文件将导出配置好的 Better Auth 客户端实例。
// convex/auth.ts import { betterAuth } from “better-auth”; import { convexAdapter } from “@better-auth/convex”; import { v } from “convex/values”; // 用于定义 Convex 数据模型 export const auth = betterAuth({ // 1. 数据库适配器 - 连接 Convex database: convexAdapter({ // 这里需要传递你的 Convex 数据库客户端或上下文 // 在 Convex 函数内部,我们可以通过 ctx.db 访问 // 适配器内部会处理具体的表操作 }), // 2. 基础配置 baseURL: process.env.NEXT_PUBLIC_APP_URL || “http://localhost:3000”, // 你的应用地址 secret: process.env.AUTH_SECRET!, // 必须,用于加密,务必设置为强随机字符串 // 3. 启用邮箱/密码认证 emailAndPassword: { enabled: true, }, // 4. 配置 OAuth 提供商(例如 GitHub) socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }, // 可以继续添加 google, discord 等 }, // 5. 配置邮件发送(用于验证邮件、重置密码等) emailVerification: { enabled: true, sendOnSignUp: true, // 注册后自动发送验证邮件 }, });这个配置有几个关键点:
baseURL:必须正确设置,否则 OAuth 回调和链接生成会出错。secret:这是最重要的安全配置。必须使用一个长且随机的字符串,并通过环境变量AUTH_SECRET管理,绝不能硬编码在代码中。- 环境变量:你需要创建
.env.local文件来存储AUTH_SECRET、GITHUB_CLIENT_ID等敏感信息。
然而,上面的代码有一个问题:convexAdapter在初始化时需要访问 Convex 的数据库实例(ctx.db),而这个ctx只在 Convex 的函数(如mutation)执行时才会被注入。因此,更常见的模式是在 Convex 的 HTTP Action 中动态创建auth实例,或者使用一个工厂函数。让我们调整一下。
3.2 创建 Convex 数据模型与适配器集成
Better Auth 需要特定的数据表来工作。我们需要在 Convex 中定义这些表的 Schema。在convex/目录下创建schema.ts。
// convex/schema.ts import { defineSchema, defineTable } from “convex/server”; import { v } from “convex/values”; export default defineSchema({ // Better Auth 需要的核心表 users: defineTable({ email: v.string(), emailVerified: v.boolean(), name: v.optional(v.string()), image: v.optional(v.string()), // ... 其他 Better Auth 需要的字段 }).index(“by_email”, [“email”]), sessions: defineTable({ userId: v.id(“users”), expiresAt: v.number(), // Convex 使用 number 表示毫秒时间戳 // ... 其他字段 }).index(“by_user_id”, [“userId”]), accounts: defineTable({ userId: v.id(“users”), provider: v.string(), providerAccountId: v.string(), // ... 其他字段 }).index(“by_provider”, [“provider”, “providerAccountId”]), // 可能还需要 verificationTokens 等表 });运行npx convex codegen来根据 schema 生成 TypeScript 类型。这些类型将帮助我们进行类型安全的数据库操作。
接下来,我们需要实现一个 Convex HTTP Action 作为 Better Auth 的 API 端点。在convex/目录下创建auth.ts(或http.ts)。
// convex/http.ts import { httpRouter } from “convex/server”; import { httpAction } from “./_generated/server”; import { betterAuth } from “better-auth”; import { convexAdapter } from “@better-auth/convex”; // 创建 HTTP 路由器 const http = httpRouter(); // 定义一个创建 auth 处理器的函数,它接收 Convex 的上下文 const createAuthHandler = (ctx: any) => { const auth = betterAuth({ database: convexAdapter({ db: ctx.db, // 将 Convex 的数据库实例传递给适配器 // 适配器内部知道如何将 Better Auth 的操作映射到我们定义的 `users`, `sessions` 等表 }), baseURL: process.env.NEXT_PUBLIC_APP_URL || “http://localhost:3000”, secret: process.env.AUTH_SECRET!, emailAndPassword: { enabled: true }, // ... 其他配置 }); return auth.handler; // 返回 Better Auth 的请求处理器 }; // 定义一个捕获所有 /api/auth/* 路由的 HTTP Action http.route({ path: “/api/auth/“, method: “POST”, // Better Auth API 主要使用 POST handler: httpAction(async (ctx, request) => { // 在这里调用 createAuthHandler,传入 ctx const handler = createAuthHandler(ctx); // 将请求转发给 Better Auth 处理器 return handler(request); }), }); // 同样处理 GET 请求(用于某些回调,如邮箱验证链接) http.route({ path: “/api/auth/“, method: “GET”, handler: httpAction(async (ctx, request) => { const handler = createAuthHandler(ctx); return handler(request); }), }); export default http;现在,所有发送到/api/auth/路径下的请求都会被这个 Convex HTTP Action 拦截,并交由 Better Auth 的核心逻辑处理。Better Auth 的convexAdapter会利用我们传入的ctx.db来执行所有数据库操作。
3.3 前端客户端集成与会话管理
服务端配置好后,前端需要与 Better Auth 的 API 进行通信。Better Auth 提供了框架特定的客户端库(如@better-auth/react),但核心是使用其通用的 JavaScript 客户端。
首先,在客户端初始化 Better Auth 客户端。我们可以在一个工具文件中创建它。
// lib/auth-client.ts import { createAuthClient } from “better-auth/react”; // 使用 React 客户端 export const authClient = createAuthClient({ baseURL: “/api/auth”, // 指向我们刚刚创建的 Convex HTTP Action 端点 });这个客户端提供了signIn,signUp,signOut,getSession等方法。
在 Next.js 的 App Router 中,我们通常希望在布局(Layout)或根组件中获取并管理用户会话状态。我们可以使用 Better Auth 的 React 钩子。
// app/providers.tsx (或 app/layout.tsx 中) “use client”; // 这是一个客户端组件 import { authClient } from “@/lib/auth-client”; import { useSession } from “better-auth/react”; export function AuthProvider({ children }: { children: React.ReactNode }) { const { data: session, status } = useSession({ client: authClient, }); if (status === “loading”) { return <div>Loading session...</div>; // 简单的加载状态 } // 你可以将会话信息通过 Context 传递给子组件 return <>{children}</>; }然后,在app/layout.tsx中包裹你的应用:
import { AuthProvider } from “@/app/providers”; export default function RootLayout({ children }) { return ( <html> <body> <AuthProvider>{children}</AuthProvider> </body> </html> ); }现在,在任何客户端组件中,你都可以使用useSession钩子来获取当前用户信息:
“use client”; import { useSession } from “better-auth/react”; import { authClient } from “@/lib/auth-client”; export default function UserProfile() { const { data: session } = useSession({ client: authClient }); if (!session?.user) { return <p>Not logged in</p>; } return ( <div> <p>Welcome, {session.user.email}!</p> <img src={session.user.image} alt=“User avatar” /> </div> ); }对于登录/注册页面,你可以直接调用authClient的方法:
“use client”; import { useState } from “react”; import { authClient } from “@/lib/auth-client”; export default function LoginPage() { const [email, setEmail] = useState(“”); const [password, setPassword] = useState(“”); const handleEmailLogin = async () => { const { error } = await authClient.signIn.email({ email, password, }); if (error) { console.error(“Login failed:”, error); // 处理错误,显示给用户 } // 登录成功,useSession 钩子会自动更新状态,页面可能重定向 }; const handleGithubLogin = async () => { await authClient.signIn.social({ provider: “github”, callbackURL: “/dashboard”, // 登录成功后跳转的页面 }); }; return ( <div> <input type=“email” value={email} onChange={(e) => setEmail(e.target.value)} /> <input type=“password” value={password} onChange={(e) => setPassword(e.target.value)} /> <button onClick={handleEmailLogin}>Sign In</button> <button onClick={handleGithubLogin}>Sign in with GitHub</button> </div> ); }当用户点击 GitHub 登录按钮时,authClient.signIn.social会引导用户跳转到 GitHub 的授权页面,授权成功后,GitHub 会将用户重定向回我们配置的 OAuth 回调地址(即/api/auth/callback/github),这个请求会被我们的 Convex HTTP Action 捕获并由 Better Auth 处理,最终完成登录流程并设置会话 Cookie。
4. 高级功能与深度配置
4.1 实现多因素认证(2FA)
Better Auth 内置了对时间基一次性密码(TOTP)2FA 的支持。启用它只需要在服务端配置中添加几行。
// 在 convex/http.ts 的 createAuthHandler 函数配置中 const auth = betterAuth({ // ... 其他配置 twoFactor: { enabled: true, // 启用 2FA // 可选:设置为 mandatory 会强制所有用户启用 2FA // mode: “optional”, // “optional” 或 “mandatory” }, });启用后,用户可以在其账户设置中扫描二维码(使用 Google Authenticator、Authy 等应用)来绑定 2FA。当 2FA 启用后,在登录流程中,输入正确的邮箱密码后,Better Auth 会返回一个状态,要求前端提供 TOTP 验证码。
前端需要相应处理这个状态:
const result = await authClient.signIn.email({ email, password, }); if (result.data?.nextStep === “2FA”) { // 提示用户输入 6 位验证码 const code = prompt(“Enter your 2FA code”); const verifyResult = await authClient.twoFactor.verify({ ticket: result.data.ticket, // 上一步返回的票据 code, }); if (verifyResult.error) { // 验证码错误 } }实操心得:2FA 的备份代码:当用户启用 2FA 时,务必提示他们安全地保存生成的备份代码(Recovery Codes)。这些代码是用户在丢失验证器应用时恢复账户的唯一途径。Better Auth 的 API 会返回这些代码,你的前端 UI 有责任以清晰、安全的方式(例如,显示在一个模态框中,并建议下载或打印)将其展示给用户。
4.2 自定义用户模型与扩展字段
你的应用很可能需要在用户对象上存储额外信息,如用户名、手机号、偏好设置等。Better Auth 支持通过schema配置扩展默认的用户模型。
首先,在 Convex 的schema.ts中为users表添加你的自定义字段:
// convex/schema.ts users: defineTable({ email: v.string(), emailVerified: v.boolean(), name: v.optional(v.string()), image: v.optional(v.string()), // 自定义字段 username: v.optional(v.string()), bio: v.optional(v.string()), role: v.string(), // 例如:“user”, “admin” }).index(“by_email”, [“email”]).index(“by_username”, [“username”]), // 为自定义字段添加索引以提高查询效率然后,在 Better Auth 配置中声明这些扩展字段,以便它在创建或更新用户时能识别和处理它们(尽管实际存储由适配器完成,但声明有助于类型安全)。
// 在 betterAuth 配置对象中 user: { schema: { username: “string?“, // 可选字符串 bio: “string?“, role: “string”, // 必填字符串,注意需要默认值或在注册流程中提供 }, },现在,当用户注册或更新资料时,你可以在 API 调用中传递这些额外字段。例如,在注册时:
await authClient.signUp.email({ email, password, name: “John Doe”, // Better Auth 标准字段 // 传递自定义字段 user: { username: “johndoe123”, role: “user”, // 通常后端会默认设置,而非前端传递 }, });重要提示:像role这样的敏感字段,绝对不应该由前端直接设置。更好的做法是:在 Convex 的mutation中,或者在 Better Auth 的hooks(后文会提到)中,由服务端逻辑来设置默认值或进行验证。
4.3 利用 Hooks 实现自定义业务逻辑
Better Auth 提供了强大的钩子(Hooks)系统,允许你在认证生命周期的关键时刻注入自定义逻辑。这是集成业务逻辑(如发送欢迎邮件、初始化用户资料、记录审计日志)的绝佳位置。
钩子是在服务端配置中定义的。例如,我们想在用户成功注册后,在 Convex 中为他创建一个关联的“用户档案”文档。
首先,在 Convex 中定义一个profiles表:
// convex/schema.ts profiles: defineTable({ userId: v.id(“users”), displayName: v.optional(v.string()), joinedAt: v.number(), }).index(“by_user_id”, [“userId”]),然后,在 Better Auth 配置中添加hooks:
// 在 convex/http.ts 的 betterAuth 配置中 hooks: { signUp: { async post({ user }) { // 这个函数在用户注册成功后执行 // 注意:这里无法直接访问 Convex 的 ctx,我们需要通过其他方式调用 Convex mutation。 // 一种模式是:在钩子内调用一个 Convex HTTP Action 或使用 Convex 的客户端。 // 更直接的方式是,将这部分逻辑移到 Convex mutation 中,由前端在注册成功后调用。 // 另一种思路是使用 Better Auth 的 `callbacks`,它可能更适合与数据库操作结合。 console.log(`User ${user.email} signed up!`); // 在实际项目中,这里可以触发一个事件或调用一个内部 API。 }, }, },由于 Better Auth 钩子执行环境(在我们的架构里是 Convex HTTP Action 的运行时)与 Convex 的数据库操作上下文(ctx)是隔离的,直接进行数据库写入可能比较棘手。更常见的模式是:
- 前端驱动:前端在调用
signUp成功后,紧接着调用一个自定义的 Convexmutation(例如createUserProfile)来初始化额外数据。 - 服务端事件:如果必须由服务端自动完成,可以考虑在 Convex 中设置一个数据库触发器(目前 Convex 支持
scheduled functions和database triggers的预览功能),监听users表的新增记录,然后自动创建profile。 - 使用 Better Auth Callbacks:查阅 Better Auth 文档,看是否有更直接的
database适配器回调(callbacks)可以在执行数据库操作前后运行,这可能能获得数据库上下文。
注意事项:钩子的执行上下文:理解你的钩子代码在哪里运行至关重要。在我们的集成中,它运行在 Convex 的 HTTP Action 环境中。这意味着你可以导入和调用其他 Convex
mutation或action,但需要像普通函数一样调用它们,并处理好异步。确保你的逻辑是幂等的(多次执行结果相同)且高效,避免阻塞主要的认证响应。
5. 部署、安全与生产环境实践
5.1 环境变量与密钥管理
生产环境的安全始于正确的配置管理。以下是你必须设置的环境变量列表(在 Vercel、Netlify 或你的服务器上):
.env.local(开发环境) / 生产环境变量:
# 应用基础 URL NEXT_PUBLIC_APP_URL=https://your-app.com # Better Auth 加密密钥,必须为强随机字符串,例如使用 `openssl rand -base64 32` 生成 AUTH_SECRET=your-super-secret-long-random-string-here # OAuth 提供商配置 GITHUB_CLIENT_ID=your_github_oauth_client_id GITHUB_CLIENT_SECRET=your_github_oauth_client_secret GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRET=... # Convex 部署相关(通常由 convex CLI 自动管理) CONVEX_DEPLOYMENT=your-convex-deployment-urlAUTH_SECRET:这是重中之重。它用于加密会话 Cookie 和令牌。如果泄露,攻击者可以伪造任意用户的会话。务必使用密码生成器创建,并在生产环境中严格保密。NEXT_PUBLIC_APP_URL:必须与你的应用实际访问地址完全一致(包括https://),否则 OAuth 回调会失败。- OAuth 密钥:在 GitHub、Google 等开发者平台创建 OAuth App 时,回调 URL(Callback URL)应设置为
{NEXT_PUBLIC_APP_URL}/api/auth/callback/{provider},例如https://your-app.com/api/auth/callback/github。
5.2 部署到生产环境
1. 部署 Convex:在项目根目录运行:
npx convex deploy这会将你的 Convex 函数(包括我们定义的 HTTP Action)和 Schema 部署到云端。CLI 会输出你的部署 URL。
2. 部署 Next.js 应用:如果你使用 Vercel,关联 Git 仓库后,Vercel 会自动检测 Next.js 项目并部署。关键是正确配置生产环境变量。
- 在 Vercel 项目的 Settings -> Environment Variables 中,添加所有必要的环境变量(
AUTH_SECRET,GITHUB_CLIENT_ID等)。 - 确保
NEXT_PUBLIC_APP_URL设置为你的 Vercel 生产域名(例如https://your-app.vercel.app)或自定义域名。
3. 关键检查清单:
- [ ] 所有环境变量已在生产环境设置,且与开发环境不同(尤其是
AUTH_SECRET)。 - [ ]
NEXT_PUBLIC_APP_URL在生产环境配置正确。 - [ ] 在 OAuth 提供商(GitHub, Google)的后台,已将生产环境的回调 URL 加入授权列表。
- [ ] 运行
npx convex deploy后,确认没有错误。 - [ ] 访问你的生产网站,测试注册、登录、OAuth 流程是否正常工作。
5.3 安全加固与最佳实践
- 使用 HTTPS:确保你的生产站点全程使用 HTTPS。这在 Vercel 等平台上默认提供。HTTPS 对防止会话劫持和中间人攻击至关重要。
- Cookie 安全:Better Auth 默认会设置安全的 Cookie 标志(
Secure,HttpOnly,SameSite=Lax)。确保你的生产环境baseURL是https://开头,这样Secure标志才会生效。HttpOnly能防止 XSS 攻击窃取 Cookie。 - 密码策略:虽然 Better Auth 会进行基础的哈希,但你可以通过配置增强策略:
emailAndPassword: { enabled: true, password: { minLength: 10, // 可以自定义正则表达式要求大小写、数字、特殊字符 // pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{10,}$/, }, }, - 速率限制:防止暴力破解。Convex 本身有默认的速率限制,但对于认证端点,你可能需要更严格的策略。可以考虑在 Convex HTTP Action 层面添加简单的 IP 频率检查,或者使用上游的 CDN(如 Vercel 的 Edge Middleware 或 Cloudflare)来设置速率限制。
- 日志与监控:在 Convex 的
mutation或action中添加日志记录,记录重要的认证事件(成功登录、失败尝试、注册等)。Convex 的 Dashboard 提供了函数日志和错误监控,便于排查问题。 - 定期更新依赖:定期运行
npm outdated并更新better-auth、@better-auth/convex和convex到稳定版本,以获取安全补丁和新功能。
6. 故障排除与常见问题
即使配置正确,在开发和部署过程中也难免会遇到问题。这里记录一些常见坑点及其解决方案。
1. OAuth 回调返回 404 或 500 错误
- 症状:点击“使用 GitHub 登录”后,跳转回你的应用时显示错误页面。
- 排查:
- 检查回调 URL:确认在 GitHub OAuth App 设置中,
Authorization callback URL完全匹配{NEXT_PUBLIC_APP_URL}/api/auth/callback/github。http和https、末尾的斜杠都不能错。 - 检查环境变量:确保生产环境的
NEXT_PUBLIC_APP_URL已正确设置,并且与 OAuth 配置中的一致。 - 查看 Convex 日志:在 Convex Dashboard 的 “Logs” 部分,查看对应 HTTP Action 的调用日志和错误信息,通常会有更详细的线索。
- 检查回调 URL:确认在 GitHub OAuth App 设置中,
2. 登录成功但会话不持久(刷新页面后退出)
- 症状:登录后页面跳转正常,但一刷新页面,
useSession又显示未登录。 - 排查:
- 检查 Cookie 域和路径:在浏览器开发者工具的 “Application” -> “Cookies” 下,查看
__session或类似名称的 Cookie 是否被正确设置。确保其Domain和Path正确。 - 检查
baseURL:确保前端authClient配置的baseURL和后端 Better Auth 配置的baseURL一致,且不含尾随斜杠。通常设置为/api/auth即可(相对路径)。 - 检查
secret:开发和生产环境使用了不同的AUTH_SECRET,会导致加解密失败。确保环境变量已正确加载。
- 检查 Cookie 域和路径:在浏览器开发者工具的 “Application” -> “Cookies” 下,查看
3. 数据库适配器错误(表不存在或字段错误)
- 症状:进行认证操作时,Convex 日志出现数据库查询错误。
- 排查:
- 运行
npx convex codegen:确保 Schema 更改后,TypeScript 类型已更新。 - 运行
npx convex deploy:确保最新的 Schema 已部署到云端。 - 检查表名和字段名:
convexAdapter期望的表名和字段名有特定格式。仔细对照@better-auth/convex适配器的文档或源码,确保你的schema.ts定义与其完全匹配。字段类型(v.string(),v.number())也必须正确。
- 运行
4. 类型错误(TypeScript)
- 症状:
createAuthHandler中ctx类型报错,或调用authClient方法时参数类型不对。 - 排查:
- 确保
npx convex codegen已运行:这能生成最新的_generated类型文件。 - 检查 Better Auth 和适配器版本:版本不匹配可能导致类型定义不一致。锁定在官方指南推荐的版本。
- 显式类型断言:在无法确定类型时,可以谨慎使用
as any或更具体的类型断言来绕过编译错误,但这只是临时手段,需尽快查明根本原因。
- 确保
5. 在钩子(Hooks)中无法进行数据库操作
- 症状:在
signUp.post钩子中尝试写入 Convex 数据库失败。 - 解决方案:如前所述,这是架构限制。采用“前端驱动”模式:在前端,
signUp成功后,链式调用一个初始化用户资料的mutation。
在const signUpResult = await authClient.signUp.email({...}); if (!signUpResult.error) { // 注册成功,调用自定义 mutation 初始化资料 await initializeUserProfile({ userId: signUpResult.data.user.id }); }convex/目录下定义这个initializeUserProfilemutation 来实现安全的服务端初始化逻辑。
遇到问题时,养成首先查看Convex Dashboard 日志和浏览器开发者工具网络面板的习惯。错误信息通常就藏在那里。对于 Better Auth 特定的问题,其官方文档和 Discord 社区是宝贵的资源。而对于 Convex 的问题,其文档和社区同样活跃且友好。这个组合虽然强大,但将两个系统深度集成,理解数据流和上下文边界是平滑开发的关键。
