当前位置: 首页 > news >正文

Convex与Better Auth集成:构建实时全栈应用的认证系统

1. 项目概述与核心价值

最近在折腾一个全栈项目,后端选型盯上了 Convex 这个实时数据库,前端自然是 Next.js。项目做到一半,用户认证这块卡壳了。市面上方案不少,但要么配置繁琐,要么和 Convex 的实时订阅、函数调用模型水土不服。直到我发现了 Better Auth 这个库,再配合官方提供的get-convex/better-auth集成包,才算真正找到了“开箱即用”的解法。这组合不是简单的功能堆砌,而是从数据模型到 API 设计都深度适配,让你能用声明式的方法,在几分钟内搭建起一套生产级、可扩展的认证系统。

简单来说,get-convex/better-auth是一个为 Convex 数据库量身定制的 Better Auth 适配器。它的核心价值在于,将 Better Auth 强大的多策略认证能力(邮箱密码、OAuth、二步验证等)无缝注入到 Convex 的生态中。你不再需要手动设计用户表、会话表,也不用操心如何将认证状态与 Convex 的实时查询(useQuery)或变更函数(mutation)安全地关联起来。这套方案帮你处理了所有底层细节,包括在 Convex 中自动创建和管理认证相关的数据表(如users,sessions,accounts),并提供了一套与 Convex 函数完美集成的客户端和服务器端工具。

对于开发者而言,这意味着你可以用你最熟悉的 Convex 开发模式——写mutationqueryaction——来处理所有认证逻辑。用户登录后,其会话信息会自然融入 Convex 的上下文,你可以在任何一个 Convex 函数里通过ctx.auth安全地获取当前用户 ID,并以此进行数据权限校验。前端方面,它提供了 React Hooks(如useSession),能让你方便地获取认证状态,并且这个状态是响应式的,可以很好地与 Convex 的实时数据流结合。无论你是做个人项目快速验证想法,还是构建需要复杂权限控制的商业应用,这套组合都能显著降低认证模块的复杂度和维护成本。

2. 整体架构与设计思路拆解

2.1 为何选择 Convex + Better Auth 组合

在评估认证方案时,我们通常会从几个维度考虑:开发体验、安全性、可扩展性以及与现有技术栈的契合度。传统的方案,例如直接使用 NextAuth.js 或 Clerk,虽然功能强大,但在与 Convex 这类具有独特数据模型和实时特性的后端服务集成时,往往需要大量的胶水代码。你需要自己搭建回调 API、处理数据库会话同步、并确保认证状态能传递给 Convex 函数,过程繁琐且容易出错。

get-convex/better-auth的设计哲学是“原生集成”。它并非一个外部服务,而是将认证逻辑作为一等公民嵌入到你的 Convex 项目中。具体体现在:

  1. 数据层原生:所有认证数据(用户、会话、OAuth 账户、验证令牌等)都存储在 Convex 数据库内,使用 Convex 的 Table 和 Index 进行定义和管理。这意味着你可以用同样的db.querydb.insert等操作来管理用户数据,并且能利用 Convex 的强大特性,如实时订阅用户状态变化。
  2. 逻辑层原生:认证的核心逻辑(如密码校验、会话创建、OAuth 回调处理)被实现为 Convex 的 HTTP Action 和 Mutation。这允许你利用 Convex 的函数部署、版本管理和日志追踪能力来运维认证服务。
  3. 上下文集成:这是最关键的一点。该集成包扩展了 Convex 的ctx(执行上下文),加入了auth对象。在任何 Mutation 或 Query 中,你都可以通过ctx.auth.getUserIdentity()ctx.auth.getUser()来获取经过验证的当前用户信息。这为数据权限控制提供了原子级的保障。

这种深度集成带来的直接好处是简化。你不需要维护额外的认证服务或复杂的中间件链条,整个应用的逻辑都统一在 Convex 的范式下,降低了认知负担和系统复杂度。

2.2 Better Auth 的核心能力与扩展性

Better Auth 本身是一个框架无关、全栈类型的认证库。它提供了一套标准的、可插拔的接口。get-convex/better-auth实现了针对 Convex 的适配器(convexAdapter),从而将 Better Auth 的抽象能力落地到具体的 Convex 数据存储和函数运行时上。

它的核心能力模块化程度很高:

  • 基础认证:邮箱+密码注册登录,包含邮箱验证、密码重置流程。
  • OAuth 集成:支持数十种社交登录提供商(GitHub, Google, Discord 等)。集成包会帮你处理好 OAuth state 的生成校验、回调处理以及将外部账户与本地用户记录关联的复杂流程。
  • 多因素认证(MFA/2FA):支持 TOTP(基于时间的一次性密码,如 Google Authenticator)等形式的二步验证,极大增强账户安全。
  • 会话管理:可配置的会话生命周期、安全地跨设备会话管理。
  • 无密码登录:通过“魔法链接”(Magic Link)等方式登录。

更重要的是其扩展性。Better Auth 允许你定义自定义数据库表结构(Schema),而 Convex 适配器能确保这些表被正确创建和索引。你可以轻松添加用户档案字段(如username,avatarUrl),或者创建与认证相关的新实体(如api_keys表用于机器访问)。所有对这些自定义字段的读写,都可以通过 Convex 的函数安全地进行。

3. 环境准备与项目初始化

3.1 创建 Convex 项目与安装依赖

假设你已经有一个 Next.js 项目(这里以 App Router 为例)。首先,你需要按照 Convex 官方指南初始化 Convex。

# 在项目根目录下,安装 Convex npm install convex # 初始化 Convex,按照提示登录或创建团队 npx convex dev

初始化过程会创建convex/目录和convex.json配置文件。接下来,安装better-auth@convex-dev/better-auth集成包。

npm install better-auth @convex-dev/better-auth

注意@convex-dev/better-auth是官方维护的包,它内部依赖better-auth并提供了 Convex 专属的适配器和工具。请确保使用这个包,而不是尝试手动组合better-auth与通用适配器。

3.2 配置 Convex 数据库 Schema

认证系统需要自己的数据表。在convex/目录下,我们创建一个名为auth.ts的文件来定义认证相关的 Schema 和初始化逻辑。但更常见的做法是,我们将 Schema 定义放在convex/schema.ts中,与业务数据 Schema 统一管理。

首先,在convex/schema.ts中,使用从集成包导入的authTables来定义认证表:

// convex/schema.ts import { authTables } from "@convex-dev/better-auth"; import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export default defineSchema({ // 使用 authTables 自动定义用户、会话、账户等核心认证表 // 这是必须的,它为 Better Auth 提供了存储后端 ...authTables, // 你可以在这里定义自己的业务数据表 // 例如,一个与用户关联的“任务”表 tasks: defineTable({ text: v.string(), isCompleted: v.boolean(), userId: v.id("users"), // 关联到 authTables 中的 users 表 }).index("by_user", ["userId"]), });

authTables是一个对象,它包含了userssessionsaccountsverifications等 Convex 表的定义。通过展开操作符...authTables将其合并到总 Schema 中,运行npx convex dev后,这些表会自动在 Convex Dashboard 中创建。

3.3 初始化 Better Auth 客户端与服务器配置

认证逻辑分为客户端和服务器端。我们需要配置一个 Better Auth 的实例。

首先,在项目根目录或lib/目录下创建auth.ts(注意,这不是 Convex 目录下的文件)。这个文件将导出配置好的 auth 客户端对象,供前后端使用。

// lib/auth.ts import { betterAuth } from "better-auth"; import { convexAdapter } from "@convex-dev/better-auth"; import * as context from "@convex-dev/better-auth/context"; export const auth = betterAuth({ // 指定数据库适配器为 Convex database: convexAdapter({ // 生产环境需要设置,开发环境 convex dev 会自动提供 deploymentUrl: process.env.CONVEX_DEPLOYMENT_URL!, }), // 配置支持的登录方式 providers: [ // 邮箱密码登录 { type: "credentials", name: "credentials", allowSignUp: true, credentials: { email: { type: "text" }, password: { type: "password" }, }, }, // GitHub OAuth 示例 { type: "oauth", name: "github", clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }, // 可以继续添加 Google, Discord 等 ], // 应用的基础 URL,用于构建回调地址等 baseUrl: process.env.NEXTAUTH_URL || "http://localhost:3000", // 邮件服务配置(用于发送验证邮件、重置密码邮件等) email: { from: "noreply@yourdomain.com", server: { host: process.env.EMAIL_SERVER_HOST, port: parseInt(process.env.EMAIL_SERVER_PORT || "587"), auth: { user: process.env.EMAIL_SERVER_USER, pass: process.env.EMAIL_SERVER_PASSWORD, }, }, }, // 会话相关配置 session: { expiresIn: 60 * 60 * 24 * 30, // 30天 updateAge: 60 * 60 * 24, // 每24小时更新一次 }, }); // 导出用于 Next.js 服务器端 API 路由的类型 export type Auth = typeof auth;

接下来,我们需要在 Convex 后端注册这个 auth 实例,以便在 Mutation 和 Query 中使用。在convex/auth.config.ts(新建)中:

// convex/auth.config.ts import { authConfig } from "@convex-dev/better-auth/server"; import { auth } from "../../lib/auth"; // 根据实际路径调整 export default authConfig({ auth, });

这个配置文件告诉 Convex 的 Better Auth 集成,如何找到你的 auth 实例。

最后,也是最关键的一步,是在 Convex 的ctx中注入 auth 上下文。修改你的convex/http.ts文件(如果没有则创建):

// convex/http.ts import { httpRouter } from "convex/server"; import { auth } from "../lib/auth"; // 根据实际路径调整 import { authRouter } from "@convex-dev/better-auth/server"; const http = httpRouter(); // 将 Better Auth 的 API 路由挂载到 /auth 路径下 // 这将自动创建 /api/auth/signin, /api/auth/callback, /api/auth/signout 等端点 http.route("/auth", authRouter(auth)); // 你可以在这里添加其他自定义的 HTTP 端点 // http.route("/custom", customRouter); export default http;

完成以上步骤后,运行npx convex dev,你的本地开发环境就拥有了完整的认证后端。访问http://localhost:3000/api/auth/signin应该能看到一个基础的登录页面(Better Auth 提供默认 UI,但通常我们会自定义)。

4. 核心功能实现与集成细节

4.1 用户注册与邮箱密码登录流程

Better Auth 默认提供了 RESTful API 端点。对于邮箱密码登录,前端需要调用/api/auth/signin/credentials(POST)。但更符合 Convex + React 开发模式的是使用集成包提供的 React Hooks。

首先,在客户端提供 Auth 上下文。在app/providers.tsx(或你的根布局组件)中:

// app/providers.tsx "use client"; import { ConvexProvider, ConvexReactClient } from "convex/react"; import { AuthProvider } from "@convex-dev/better-auth/react"; import { convex } from "@/convex/_generated/api"; // 由 `npx convex dev` 生成 import { authClient } from "@/lib/auth-client"; // 我们需要创建这个 const convexClient = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); export function Providers({ children }: { children: React.ReactNode }) { return ( <ConvexProvider client={convexClient}> <AuthProvider client={authClient}>{children}</AuthProvider> </ConvexProvider> ); }

创建lib/auth-client.ts

// lib/auth-client.ts import { createAuthClient } from "@convex-dev/better-auth/react"; export const authClient = createAuthClient({ // 指向我们之前在 http.ts 中定义的路由 baseUrl: process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000", });

现在,在任意客户端组件中,你就可以使用 Hooks 了:

// app/login/page.tsx "use client"; import { useSignIn } from "@convex-dev/better-auth/react"; import { useState } from "react"; export default function LoginPage() { const signIn = useSignIn(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); const result = await signIn.credentials({ email, password, callbackURL: "/dashboard", // 登录成功后跳转的页面 }); setIsLoading(false); if (result.error) { // 处理错误,例如密码错误、用户不存在等 alert(result.error.message); } // 成功后会自动重定向并更新会话状态 }; return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" required /> <button type="submit" disabled={isLoading}> {isLoading ? "登录中..." : "登录"} </button> </form> ); }

注册流程类似,调用useSignUpHook。所有的网络请求、Cookie 设置、会话管理都由better-auth@convex-dev/better-auth在背后处理。

4.2 社交登录(OAuth)集成实战

以 GitHub OAuth 为例,你需要在 GitHub Developer Settings 中创建一个 OAuth App。获取Client IDClient Secret,填入环境变量(.env.local):

GITHUB_CLIENT_ID=your_github_client_id GITHUB_CLIENT_SECRET=your_github_client_secret NEXTAUTH_URL=http://localhost:3000

lib/auth.tsproviders数组中已经配置了 GitHub。前端添加一个登录按钮:

// app/login/page.tsx (部分代码) import { useSignIn } from "@convex-dev/better-auth/react"; export default function LoginPage() { const signIn = useSignIn(); // ... const handleGithubLogin = () => { signIn.social({ provider: "github", callbackURL: "/dashboard", }); }; return ( <div> {/* ... 邮箱密码表单 ... */} <button onClick={handleGithubLogin}>使用 GitHub 登录</button> </div> ); }

当用户点击按钮时,better-auth会自动处理重定向到 GitHub 授权页面、获取授权码、并在回调 URL (/api/auth/callback/github) 处用 Convex Action 交换access_token和获取用户信息,最后在 Convex 的usersaccounts表中创建或关联记录。整个过程无需你编写任何 OAuth 流程代码。

实操心得:环境变量与回调 URL:OAuth 配置中最常见的坑是回调 URL(Callback URL)不匹配。在 GitHub 等平台配置 OAuth App 时,授权的回调 URL 必须精确匹配{baseUrl}/api/auth/callback/{provider}baseUrl在开发环境是http://localhost:3000,生产环境是你的域名。确保环境变量NEXTAUTH_URLbaseUrl配置正确,否则会导致授权失败。

4.3 在 Convex 函数中获取用户身份与权限控制

这是集成包最强大的特性之一。在任何 Convex Mutation 或 Query 中,你都可以通过ctx.auth访问当前请求的认证信息。

首先,确保你的 Convex 函数(例如convex/tasks.ts)能够接收到扩展后的ctx。这需要你在convex/_generated/server.d.ts被正确生成(通常运行npx convex dev会自动处理)。然后:

// convex/tasks.ts import { v } from "convex/values"; import { mutation, query } from "./_generated/server"; import { getAuthUserId } from "@convex-dev/better-auth/server"; // 一个辅助函数 // 创建一个新任务,并自动关联到当前登录用户 export const createTask = mutation({ args: { text: v.string(), }, handler: async (ctx, args) => { // 方法1: 获取用户 ID const userId = await getAuthUserId(ctx); if (!userId) { throw new Error("用户未认证,无法创建任务"); } // 方法2: 获取完整的用户身份对象(包含 token 中的信息) // const identity = await ctx.auth.getUserIdentity(); // if (!identity) { ... } // 将任务插入数据库,并关联 userId const taskId = await ctx.db.insert("tasks", { text: args.text, isCompleted: false, userId, // 使用获取到的 userId }); return taskId; }, }); // 查询当前用户的所有任务 export const getMyTasks = query({ handler: async (ctx) => { const userId = await getAuthUserId(ctx); if (!userId) { // 未登录用户返回空数组或 null return []; } // 使用定义在 schema 中的索引高效查询 const tasks = await ctx.db .query("tasks") .withIndex("by_user", (q) => q.eq("userId", userId)) .collect(); return tasks; }, });

getAuthUserId(ctx)是一个便捷辅助函数,它内部调用了ctx.auth.getUserIdentity()并提取subject(即用户ID)。这个userId对应users表中的_id字段,你可以用它来建立数据关联和进行权限校验。

注意事项:Query 中的认证:在 Query 函数中,认证失败通常不应抛出错误,而是返回空数据或未认证状态,因为 Query 可能被用于公共页面。前端 Hooks (useQuery) 需要能处理这种情况。而在 Mutation 中,对于必须认证的操作,直接抛出错误是合理的,前端会收到错误信息。

4.4 二步验证(2FA)增强安全

Better Auth 内置了 TOTP 二步验证支持。启用后,用户在输入密码后,还需要提供来自验证器应用(如 Google Authenticator、Authy)的动态码。

首先,在lib/auth.ts的配置中启用 2FA:

// lib/auth.ts export const auth = betterAuth({ // ... 其他配置 ... twoFactor: { enabled: true, // 启用 2FA // 可选:强制某些用户或所有用户启用 // mandatory: true, }, });

然后,在用户账户设置页面,你需要提供两个功能:1. 启用 2FA;2. 验证并确认启用。

前端组件示例(启用 2FA):

// app/settings/security/page.tsx "use client"; import { useSession, useTwoFactor } from "@convex-dev/better-auth/react"; import { useState } from "react"; export default function SecuritySettings() { const { data: session } = useSession(); const { generate, verify, disable } = useTwoFactor(); const [qrCodeUri, setQrCodeUri] = useState<string | null>(null); const [secret, setSecret] = useState<string | null>(null); const [verificationCode, setVerificationCode] = useState(""); const handleEnable2FA = async () => { const result = await generate(); if (result.data) { setQrCodeUri(result.data.uri); // 用于生成二维码的 URI setSecret(result.data.secret); // 明文密钥,用于手动输入 } }; const handleVerify2FA = async () => { const result = await verify({ code: verificationCode, }); if (result.success) { alert("二步验证已成功启用!"); setQrCodeUri(null); setSecret(null); setVerificationCode(""); } else { alert("验证码错误,请重试。"); } }; return ( <div> <h2>二步验证 (2FA)</h2> {session?.user?.twoFactorEnabled ? ( <div> <p>✅ 二步验证已启用。</p> <button onClick={() => disable()}>禁用 2FA</button> </div> ) : ( <div> <p>二步验证未启用。</p> {!qrCodeUri ? ( <button onClick={handleEnable2FA}>启用 2FA</button> ) : ( <div> <p>请使用验证器应用扫描下方二维码,或手动输入密钥:</p> {/* 使用第三方库如 `qrcode.react` 显示二维码 */} {/* <QRCodeSVG value={qrCodeUri} /> */} <p>密钥: {secret}</p> <input type="text" value={verificationCode} onChange={(e) => setVerificationCode(e.target.value)} placeholder="输入验证器中的 6 位数字" /> <button onClick={handleVerify2FA}>验证并启用</button> </div> )} </div> )} </div> ); }

当 2FA 启用后,用户登录流程会多出一个步骤。在输入密码后,better-auth的 API 会返回一个状态,要求前端提供 TOTP 代码。你需要在前端登录逻辑中处理这个状态。

// 在登录逻辑中处理 2FA 挑战 const result = await signIn.credentials({ email, password }); if (result.data?.challenge === "two_factor") { // 需要跳转到 2FA 验证页面,或在本页面弹出输入框 setShow2FACodeInput(true); setPendingSessionToken(result.data.sessionToken); // 保存会话令牌用于后续验证 } // 然后,在用户输入 TOTP 码后 const verifyResult = await signIn.twoFactor({ code: twoFactorCode, sessionToken: pendingSessionToken, });

整个过程,包括密钥生成、验证、会话状态管理,都由better-auth和 Convex 后端自动处理,你只需要按照 Hook 提供的接口调用即可。

5. 高级特性与自定义扩展

5.1 自定义用户数据与数据库扩展

默认的users表可能只包含email,emailVerified,name,image等基础字段。在实际项目中,我们经常需要存储更多信息,如用户名、手机号、个人简介等。

Better Auth 支持通过schema配置项扩展数据库模型。修改lib/auth.ts

// lib/auth.ts export const auth = betterAuth({ database: convexAdapter({...}), // 扩展数据库 Schema schema: { users: { // 在默认字段基础上,添加自定义字段 username: { type: "string", unique: true }, bio: { type: "string", maxLength: 500 }, dateOfBirth: { type: "date", optional: true }, // 注意:这些字段需要在 convex/schema.ts 的 authTables 扩展中体现吗? // 实际上,convexAdapter 会基于此 schema 自动同步到 Convex 表定义。 }, // 你甚至可以添加全新的表 // profiles: { // userId: { type: "string", references: "users" }, // website: { type: "string", optional: true }, // }, }, // ... 其他配置 });

但是,对于 Convex 适配器,更推荐的做法是将业务相关的扩展字段存储在单独的表中,并通过userId关联。这是因为认证核心表的结构相对稳定,与业务逻辑解耦更清晰。例如,在convex/schema.ts中定义:

// convex/schema.ts export default defineSchema({ ...authTables, // 用户档案表,与 users 表一对一关联 userProfiles: defineTable({ userId: v.id("users"), username: v.string(), bio: v.optional(v.string()), avatarUrl: v.optional(v.string()), }).index("by_userId", ["userId"]), });

然后,在用户注册后,通过一个 Convex Mutation 来创建对应的userProfiles记录。你可以在lib/auth.ts中配置callbacks来触发这个 Mutation:

// lib/auth.ts export const auth = betterAuth({ // ... callbacks: { // 用户注册成功后回调 async onUserCreate(user, request) { // 在这里调用一个 Convex Mutation 来创建用户档案 // 注意:这是一个服务器端回调,可以安全地调用 Convex // 你需要导入你的 Convex 客户端实例 const convexClient = ... // 在服务器端初始化 Convex 客户端 await convexClient.mutation("createUserProfile", { userId: user.id, username: user.email.split('@')[0], // 示例:用邮箱前缀作为默认用户名 }); return user; }, }, });

5.2 邮件服务与自定义邮件模板

账户激活、密码重置、登录安全警报等功能都依赖邮件。Better Auth 内置了邮件发送逻辑,但需要你配置邮件服务器(如 SMTP)或邮件服务商 API(如 Resend, SendGrid)。

以 Resend 为例:

// lib/auth.ts import { resend } from "better-auth/adapters/email/resend"; export const auth = betterAuth({ // ... email: { from: "Your App <onboarding@yourdomain.com>", server: resend({ apiKey: process.env.RESEND_API_KEY!, }), // 自定义邮件模板 templates: { signIn: { body: (data) => `你的登录验证码是: ${data.token}`, subject: "登录你的账户", }, resetPassword: { body: (data) => `请点击以下链接重置密码: ${data.url}`, subject: "重置你的密码", }, // ... 其他模板 }, }, });

如果你使用传统的 SMTP 服务器(如公司邮箱、AWS SES SMTP 接口),配置如下:

email: { from: "noreply@yourdomain.com", server: { host: "smtp.your-email-provider.com", port: 587, auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASSWORD, }, }, },

注意事项:邮件发送与事务:在用户注册流程中,发送验证邮件和创建用户记录应该是一个原子操作。Better Auth 在 Convex 适配器内部,通过将邮件发送逻辑放在 Convex Mutation 中,确保了如果邮件发送失败,整个用户创建事务可以回滚(或标记为待验证状态),避免了数据不一致。

5.3 会话管理、安全与吊销

会话安全是认证系统的核心。better-auth提供了细致的控制:

  • 会话过期与更新:通过session.expiresInsession.updateAge控制。updateAge定义了多久自动更新一次会话有效期,避免用户活跃时突然掉线。
  • 多设备管理:用户可以在安全设置页面查看所有活跃会话(设备、浏览器、IP、最后活动时间),并可以选择吊销(登出)特定设备。
  • 安全事件记录:重要的安全操作(如密码修改、启用2FA、新设备登录)可以触发日志记录,方便用户审计。

在前端,你可以提供一个“账户安全”页面,使用useSessionuseUpdateSession等 Hook 来展示和管理会话:

// app/settings/sessions/page.tsx "use client"; import { useSession, useSignOut } from "@convex-dev/better-auth/react"; export default function SessionsPage() { const { data: session } = useSession(); const signOut = useSignOut(); const { sessions, isLoading } = useSessionList(); // 假设有这样一个 Hook,实际可能需要自定义查询 const revokeSession = async (sessionId: string) => { // 调用一个 Convex Mutation 来吊销特定会话 // await convexClient.mutation("revokeSession", { sessionId }); }; return ( <div> <h2>活跃会话</h2> <p>当前登录设备: {session?.user?.name} on {session?.device}</p> <ul> {/* 遍历 sessions */} {sessions?.map((s) => ( <li key={s.id}> {s.device} - {s.ip} - 最后活动: {new Date(s.lastActiveAt).toLocaleString()} {s.id !== session?.id && ( <button onClick={() => revokeSession(s.id)}>吊销此会话</button> )} </li> ))} </ul> <button onClick={() => signOut({ callbackURL: "/" })}>全局登出(所有设备)</button> </div> ); }

为了实现会话列表和吊销功能,你需要在 Convex 中创建相应的 Query 和 Mutation,查询sessions表并根据userId过滤,以及删除指定的会话记录。这些操作都应通过ctx.auth进行严格的权限检查,确保用户只能操作自己的会话。

6. 部署、监控与性能调优

6.1 生产环境部署要点

将使用get-convex/better-auth的应用部署到生产环境,需要关注以下几点:

  1. 环境变量:确保所有敏感信息(CONVEX_DEPLOYMENT_URL,GITHUB_CLIENT_SECRET,RESEND_API_KEY, 数据库连接字符串等)都设置在部署平台的环境变量中,而不是硬编码在代码里。
  2. Convex 部署:运行npx convex deploy将你的 Convex 函数和 Schema 部署到生产环境。Convex CLI 会自动处理依赖和构建。
  3. Next.js 部署:将你的 Next.js 应用部署到 Vercel、Netlify 或任何 Node.js 托管平台。确保构建命令正确,且环境变量已配置。
  4. Base URL 配置:生产环境中,lib/auth.ts里的baseUrl必须设置为你的生产域名(如https://yourapp.com)。这直接影响 OAuth 回调 URL 和 Cookie 域的设置。
  5. Cookie 安全:在生产环境,Better Auth 会自动使用安全 Cookie(secure: true)。确保你的生产站点使用 HTTPS。
  6. 数据库索引authTables已经为核心查询创建了必要的索引。但如果你添加了大量自定义查询(例如按用户名搜索),记得在convex/schema.ts中为相应的表添加索引,以保障性能。

6.2 监控与日志

Convex Dashboard 提供了强大的函数执行监控和日志查看功能。所有认证相关的 Mutation 和 Action(如signIn,callback,sessionUpdate)都会在这里留下记录。

  • 错误排查:如果用户登录失败,你可以在 Convex Dashboard 的Logs部分查看对应 HTTP Action 的详细错误信息,是网络问题、配置错误还是代码异常一目了然。
  • 性能分析:在Metrics部分,可以看到每个函数的调用频率和耗时。如果发现某个认证函数(如 OAuth 回调)特别慢,可能需要检查网络或第三方 API 的响应速度。
  • 审计日志:对于安全要求高的应用,可以考虑在关键操作(如成功登录、密码修改、启用2FA)的 Convex Mutation 中,额外向一个audit_logs表插入记录,记录操作时间、用户ID、IP地址和操作类型,便于事后审计。

6.3 性能优化与扩展性考量

  • 会话存储:默认会话存储在 Convex 数据库中。对于超高并发应用,这可能会成为瓶颈。虽然 Convex 本身性能很好,但你可以考虑 Better Auth 支持的 外部会话存储适配器 (如 Redis),不过目前@convex-dev/better-auth可能还未集成此适配器,需要关注官方更新。
  • 缓存策略:用户信息(如name,image)在会话 token 中已有缓存。对于频繁访问但很少变更的用户资料,可以在前端使用 React Query 或 SWR 进行本地缓存,减少对ctx.auth.getUser()的调用。
  • 数据库查询优化:在 Convex Query 中,始终使用索引进行查询。例如,通过userId查询用户任务时,确保使用了by_user索引。避免全表扫描。
  • 无服务器函数冷启动:Convex 函数作为无服务器函数运行,会有冷启动延迟。对于登录这种对延迟敏感的操作,其性能在活跃状态下通常很好。可以通过设置 Convex 的keepAlive配置来为关键函数保持实例温暖,但这会产生额外成本。

7. 常见问题排查与调试技巧

在实际集成和开发过程中,你可能会遇到一些典型问题。这里记录了几个我踩过的坑和解决方法。

7.1 认证失败与错误代码

问题现象可能原因排查步骤与解决方案
登录时提示Invalid credentials1. 邮箱不存在或密码错误。
2. 用户未验证邮箱(如果设置了emailVerification)。
3. 密码哈希算法不匹配(如果从其他系统迁移用户)。
1. 检查 Convex Dashboard 的users表,确认用户存在且邮箱正确。
2. 检查emailVerified字段是否为null,需要调用auth.verifyEmail相关接口。
3. 确保better-auth的密码哈希配置与旧系统一致,或引导用户重置密码。
OAuth 登录失败,重定向回登录页且无错误1. OAuth 提供商配置错误(Client ID/Secret、回调 URL)。
2. 环境变量未正确加载。
3. 在lib/auth.ts中未正确引入或配置 provider。
1.仔细核对GitHub/Google 等后台配置的回调 URL,必须与baseUrl + /api/auth/callback/{provider}完全一致(包括 http/https)。
2. 在服务器端(如 Next.js API Route 或 Convex Action 日志)查看详细错误。Convex Dashboard 的 Logs 里会有 OAuth 回调 Action 的失败信息。
3. 确认providers数组中的name字段与 Better Auth 预期的名称匹配(如"github","google")。
在 Convex Mutation 中ctx.auth.getUserIdentity()返回null1. 请求未携带有效的会话 Cookie。
2. 该 Mutation 未被正确保护,允许了未认证的调用。
3. 前端 Convex 客户端未与 AuthProvider 正确集成。
1. 检查浏览器开发者工具的Application->Cookies,确认是否有authjs.session-token等 Cookie。
2. 确保前端调用 Mutation 时,Convex 客户端是从被AuthProvider包裹的组件中使用的。useQueryuseMutation必须在AuthProvider子树内。
3. 在 Mutation 开头添加console.log或查看 Convex Dashboard 日志,确认请求上下文。
用户注册后收不到验证邮件1. 邮件服务器配置错误(SMTP 参数、API Key)。
2. 邮件被标记为垃圾邮件。
3.email.from地址未验证(对于 SES、SendGrid 等服务)。
1. 检查lib/auth.ts中的email.server配置,确保主机、端口、认证信息正确。
2. 查看邮件服务商的后台,是否有发送失败记录或退信。
3. 对于云邮件服务,确保发件人邮箱地址已通过验证或域名已配置 SPF/DKIM 记录。

7.2 开发与调试实用命令

  • 清理 Convex 数据:在开发时,有时需要重置数据库。可以在 Convex Dashboard 的Data部分手动删除表,或者使用 CLI:npx convex run --prod deleteAllData(谨慎使用,此命令会删除生产数据!开发环境可用npx convex run deleteAllData)。
  • 检查生成的 Schema:运行npx convex codegen会更新convex/_generated下的类型定义。如果修改了schema.tslib/auth.ts中的schema配置,记得运行此命令,让 TypeScript 类型同步更新,避免类型错误。
  • 查看实时日志:在终端运行npx convex dev会输出实时日志。更详细的日志需要到 Convex Dashboard 的Logs页面查看,这里可以按函数、时间过滤,是排查问题的第一现场。
  • 测试 HTTP 端点:可以使用curl或 Postman 直接测试你的认证 API。例如,测试邮箱登录:curl -X POST http://localhost:3000/api/auth/signin/credentials -H "Content-Type: application/json" -d '{"email":"test@example.com","password":"password"}'。观察返回的状态码和响应体。

7.3 类型安全与 TypeScript 配置

为了获得最佳的类型提示,确保你的tsconfig.json包含了 Convex 的生成类型路径:

{ "compilerOptions": { // ... 其他配置 ... "paths": { "@/*": ["./*"], "convex/_generated": ["./convex/_generated"] // 确保这一行存在 } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] }

在编写调用 Convex 函数的代码时,从convex/_generated/api导入函数名,可以获得完整的参数和返回值类型提示。

import { api } from "@/convex/_generated/api"; import { useMutation } from "convex/react"; const createTask = useMutation(api.tasks.createTask); // `createTask` 现在具有类型 `(args: { text: string }) => Promise<Id<"tasks">>`

对于ctx.auth的类型,@convex-dev/better-auth/server包已经扩展了 Convex 的QueryCtxMutationCtx。只要在convex/http.ts中正确配置了路由,并在convex/auth.config.ts中导入了配置,类型系统就会自动识别。

如果在 Convex 函数中访问ctx.auth时 TypeScript 报错“属性auth不存在”,请检查:

  1. 是否运行了npx convex devnpx convex codegen来更新类型?
  2. convex/_generated/server.d.ts文件是否被正确生成?可以打开该文件查看QueryCtxMutationCtx接口是否包含了auth属性。

这套组合拳打下来,从开发体验、功能完整性到生产稳定性,都给了我足够的信心。它可能不是所有场景下的银弹,但对于那些已经在 Convex 生态内、寻求快速构建安全可靠认证系统的团队来说,无疑是当前最优雅的解决方案之一。

http://www.cnnetsun.cn/news/2186258.html

相关文章:

  • 如何用Zotero Style插件实现文献管理革命:5分钟打造智能学术工作流
  • 终极指南:在VMware中快速解锁macOS虚拟机支持的完整教程
  • Windows右键菜单管理工具ContextMenuManager:系统菜单优化与自定义指南
  • WeChatPad:终极微信双设备登录解决方案,强制启用平板模式实现手机平板同时在线
  • Ubuntu 20.04下搞定gici-open编译:从glog报错到ceres版本冲突的保姆级排坑指南
  • 高效解锁Windows多用户远程桌面:RDPWrap完整实用指南
  • SR501人体感应模块在Linux下的三种玩法:从基础驱动到MQTT上报,玩转物联网边缘节点
  • 保姆级教程:用NTU RGB+D 120数据集快速上手骨架行为识别(附完整动作标签清单)
  • Joy-Con Toolkit终极指南:免费解锁Switch手柄隐藏功能
  • 嵌入式系统在工业自动化中的关键技术与应用
  • 本地AI编程助手SwiftIDE:私有化部署与IDE集成实践
  • 保姆级教程:在ROS Noetic上为你的机器人接入科大讯飞星火大模型(附完整代码)
  • Cursor IDE智能体编排插件:构建AI虚拟开发团队工作流
  • CTF实战:如何从TTL字段中提取隐藏图片(附Python代码)
  • 5分钟搞定Switch手柄PC连接:BetterJoy让你的任天堂手柄变身高性能Xbox控制器
  • PCB设计避坑指南:高速信号线为什么不能跨分割走线?附PADS/Altium实战案例
  • MAA明日方舟助手:终极自动化战斗与基建管理完整指南
  • 他用排行第一的降 AI 软件 35 分钟过了知网 AIGC 检测,靠的不是运气。
  • 零代码构建AI智能体:agentforge-openclaw核心架构与实战指南
  • 日志分析告警失效真相大起底(2026年MCP新规强制适配倒计时47天)
  • Cat-Catch 2.5.9:浏览器资源嗅探的终极解决方案
  • BetterGI原神AI辅助工具:释放双手,让游戏回归纯粹乐趣
  • 软件工程师在TVA产业化浪潮中的角色定位与机遇(3)
  • 【紧急预警】监管新规生效倒计时30天!用R语言快速完成欧盟AI Act第10条偏见验证:卡方独立性检验+后验预测检查PPC全流程
  • 告别CUDA依赖:用OpenCL在AMD/Intel/NVIDIA显卡上跑通你的第一个异构计算程序
  • 用Python玩转Jetson Nano串口:一个脚本实现数据收发与回显测试
  • 探索小红书内容宇宙:5个颠覆性方法深度挖掘数据价值
  • 保姆级图解:HDMI音频数据包如何从采样到传输(附N/CTS同步原理)
  • Grinn ReneSOM-V2H边缘AI模块解析与应用
  • 告别手动刷写!用CAPL脚本在CANoe测试模块中自动化调用Vflash文件(附完整代码)