2026 年,Passkey 登录率同比增长 400%,OAuth 2.1 成为新标准,而传统的「自己写 JWT + Session」方案正在被开发者抛弃。Better Auth 作为一个 TypeScript 原生的全栈认证框架,在 GitHub 上已经超过 15k Star,成为 Next.js、Nuxt、SvelteKit 等全栈框架中最受欢迎的认证解决方案之一。它用一个框架覆盖了邮箱登录、社交 OAuth、Passkey 无密码认证、双因素验证、多租户组织管理等几乎所有认证场景,且完全自托管、数据不出服务器。
🔐 一、为什么选择 Better Auth 而不是自己写
认证方案全景对比
在选择认证方案之前,先看一组关键数据:
| 方案 | 开发周期 | Passkey 支持 | OAuth 支持 | 2FA 支持 | 自托管 | 数据控制 |
|---|---|---|---|---|---|---|
| 手写 JWT + Session | 2-4 周 | ❌ 需自己实现 | ❌ 需自己实现 | ❌ 需自己实现 | ✅ | ✅ |
| Auth.js (NextAuth) | 1-3 天 | ⚠️ 需额外库 | ✅ | ⚠️ 需额外库 | ✅ | ✅ |
| Clerk | 1 小时 | ✅ | ✅ | ✅ | ❌ SaaS | ❌ 第三方 |
| Supabase Auth | 1 小时 | ✅ | ✅ | ✅ | ⚠️ 需自建 | ⚠️ 依赖 Supabase |
| Better Auth | 半天 | ✅ 原生 | ✅ 原生 | ✅ 原生 | ✅ | ✅ |
⚠️ **警告:**手写认证系统是 Web 安全中最常见的错误之一。OAuth state 参数处理、CSRF 防护、Session 固定攻击防御——每一个细节都可能成为安全漏洞。除非你是安全专家,否则请使用成熟的认证框架。
Better Auth 的核心优势
Better Auth 的设计哲学是「一个框架解决所有认证问题」:
- ✅ TypeScript 原生:端到端类型安全,从数据库 Schema 到 API 调用全链路推导
- ✅ 框架无关:支持 Next.js、Nuxt、SvelteKit、Astro、Remix、SolidStart、Hono 等
- ✅ 数据库灵活:内置 Drizzle、Prisma、Kysely 适配器,也支持原生 SQL
- ✅ 插件架构:核心精简,功能通过插件按需加载(Passkey、Organization、2FA 等)
- ✅ 零第三方依赖运行:不依赖任何 SaaS 服务,数据完全在你自己的数据库中
💡 **提示:**Better Auth 的插件系统是它区别于 Auth.js 的最大优势。Auth.js 的 Provider 模式只关注「登录方式」,而 Better Auth 的插件可以扩展认证系统的任何维度——从多租户组织管理到管理后台 API。
🚀 二、从零搭建 Better Auth 认证系统
项目初始化与核心配置
以 Next.js 15 + PostgreSQL + Drizzle ORM 为例,从零搭建一个完整的认证系统:
# 创建项目并安装依赖
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm install better-auth drizzle-orm pg
npm install -D drizzle-kit @types/pg
首先配置 Better Auth 的服务端核心:
// lib/auth.ts — Better Auth 核心配置
import { betterAuth } from "better-auth";
import { passkey, twoFactor, organization } from "better-auth/plugins";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "./db"; // Drizzle 数据库实例
export const auth = betterAuth({
// 基础配置
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
secret: process.env.BETTER_AUTH_SECRET!,
// 数据库适配器 — 使用 Drizzle + PostgreSQL
database: drizzleAdapter(db, {
provider: "pg",
}),
// 邮箱密码认证
emailAndPassword: {
enabled: true,
requireEmailVerification: true, // 生产环境必须开启
minPasswordLength: 8,
maxPasswordLength: 128,
},
// 社交 OAuth 登录
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
// Session 配置
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 天过期
updateAge: 60 * 60 * 24, // 每天刷新一次
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Cookie 缓存 5 分钟,减少数据库查询
},
},
// 插件系统 — 按需加载
plugins: [
passkey(), // Passkey 无密码认证
twoFactor(), // 双因素认证(TOTP)
organization(), // 多租户组织管理
],
// 高级安全配置
rateLimit: {
window: 60, // 60 秒窗口
max: 10, // 最多 10 次请求
},
advanced: {
generateId: () => crypto.randomUUID(), // 使用 UUID 作为用户 ID
crossSubDomainCookies: {
enabled: false, // 生产环境按需开启
},
},
});
📌 记住:
BETTER_AUTH_SECRET必须是一个至少 32 字符的随机字符串。在生产环境中使用openssl rand -base64 32生成,并存储在环境变量中,永远不要硬编码在代码里。
API 路由与客户端初始化
Better Auth 提供了开箱即用的 API 路由处理器:
// app/api/auth/[...all]/route.ts — Better Auth API 路由
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
// Better Auth 自动生成以下 API 端点:
// POST /api/auth/sign-up/email — 邮箱注册
// POST /api/auth/sign-in/email — 邮箱登录
// POST /api/auth/sign-in/social — 社交登录
// POST /api/auth/sign-out — 登出
// GET /api/auth/get-session — 获取当前 Session
// POST /api/auth/passkey/* — Passkey 注册/验证
// POST /api/auth/two-factor/* — 双因素认证
// POST /api/auth/organization/* — 组织管理
export const { GET, POST } = toNextJsHandler(auth);
客户端使用 createAuthClient 初始化:
// lib/auth-client.ts — 客户端认证 SDK
import { createAuthClient } from "better-auth/react";
import {
passkeyClient,
twoFactorClient,
organizationClient,
} from "better-auth/client/plugins";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
plugins: [
passkeyClient(),
twoFactorClient(),
organizationClient(),
],
});
// 导出类型安全的方法
export const {
signIn,
signUp,
signOut,
useSession,
passkey,
twoFactor,
organization,
} = authClient;
注册与登录组件
// components/auth-form.tsx — 完整的注册/登录表单
"use client";
import { useState } from "react";
import { authClient } from "@/lib/auth-client";
export function AuthForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const [isSignUp, setIsSignUp] = useState(false);
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setLoading(true);
try {
if (isSignUp) {
const { error } = await authClient.signUp.email({
email,
password,
name,
});
if (error) throw new Error(error.message);
} else {
const { error } = await authClient.signIn.email({
email,
password,
});
if (error) throw new Error(error.message);
}
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
// GitHub 一键登录
const handleGitHubLogin = async () => {
await authClient.signIn.social({ provider: "github" });
};
// Passkey 登录
const handlePasskeyLogin = async () => {
const { error } = await authClient.passkey.signIn();
if (error) setError(error.message);
};
return (
<form onSubmit={handleSubmit} className="space-y-4 max-w-sm mx-auto">
{isSignUp && (
<input
type="text"
placeholder="姓名"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full p-2 border rounded"
required
/>
)}
<input
type="email"
placeholder="邮箱"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-2 border rounded"
required
/>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-2 border rounded"
minLength={8}
required
/>
{error && <p className="text-red-500 text-sm">{error}</p>}
<button
type="submit"
disabled={loading}
className="w-full p-2 bg-blue-600 text-white rounded"
>
{loading ? "处理中..." : isSignUp ? "注册" : "登录"}
</button>
<div className="flex gap-2">
<button
type="button"
onClick={handleGitHubLogin}
className="flex-1 p-2 bg-gray-800 text-white rounded"
>
GitHub 登录
</button>
<button
type="button"
onClick={handlePasskeyLogin}
className="flex-1 p-2 bg-green-600 text-white rounded"
>
Passkey 登录
</button>
</div>
<p className="text-center text-sm">
{isSignUp ? "已有账号?" : "没有账号?"}
<button
type="button"
onClick={() => setIsSignUp(!isSignUp)}
className="text-blue-600 underline ml-1"
>
{isSignUp ? "去登录" : "去注册"}
</button>
</p>
</form>
);
}
✅ **推荐做法:**在登录页面同时提供邮箱密码、OAuth 和 Passkey 三种方式。2026 年的数据显示,提供 Passkey 选项的网站登录转化率平均提升 35%。
🔧 三、高级功能实战
Passkey 无密码认证集成
Passkey 是 WebAuthn 标准的具体实现,用设备的生物识别(指纹、面容)替代密码。Better Auth 对 Passkey 的封装非常优雅:
// 服务端 — 注册新 Passkey(在用户已登录状态下调用)
// POST /api/auth/passkey/register
async function registerPasskey(userId: string) {
// Better Auth 自动处理 Challenge 生成和验证
// 前端调用:
// const { data, error } = await authClient.passkey.addPasskey({
// name: "MacBook Pro Touch ID"
// });
}
// 中间件 — 检查 Session 并保护路由
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
export async function middleware(request: NextRequest) {
const session = await auth.api.getSession({
headers: request.headers,
});
// 受保护路由列表
const protectedPaths = ["/dashboard", "/settings", "/admin"];
const isProtected = protectedPaths.some((path) =>
request.nextUrl.pathname.startsWith(path)
);
if (isProtected && !session) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/settings/:path*", "/admin/:path*"],
};
双因素认证(2FA)完整流程
Better Auth 的 2FA 插件支持 TOTP(基于时间的一次性密码),兼容 Google Authenticator、Authy 等所有标准 TOTP 应用:
// 启用 2FA 的完整流程
import { authClient } from "@/lib/auth-client";
// 步骤 1:生成 TOTP 密钥和二维码
async function enable2FA() {
const { data, error } = await authClient.twoFactor.enable({
password: "用户的当前密码", // 安全起见需要验证密码
});
// data.totpURI — 用于生成二维码的 otpauth:// URI
// data.secret — TOTP 密钥(备用)
return data;
}
// 步骤 2:验证 TOTP 代码以完成启用
async function verify2FA(code: string) {
const { error } = await authClient.twoFactor.verifyTOTP({
code, // 6 位数字验证码
});
if (!error) {
// 2FA 启用成功,建议用户保存恢复码
// data.backupCodes — 一次性恢复码,务必保存
}
}
// 步骤 3:登录时的 2FA 验证
async function signInWith2FA(email: string, password: string) {
const { data, error } = await authClient.signIn.email({
email,
password,
});
// 如果用户开启了 2FA,返回值中会包含 twoFactorRedirect
if (data?.twoFactorRedirect) {
// 需要用户输入 TOTP 代码
// 跳转到 2FA 验证页面
const { error: verifyError } = await authClient.twoFactor.verifyTOTP({
code: "用户输入的6位验证码",
});
}
}
⚠️ **警告:**2FA 启用后一定要提示用户保存恢复码(Backup Codes)。如果用户丢失了 TOTP 设备又没有恢复码,将无法登录。这是用户体验中最常见的坑点之一。
多租户组织管理
Better Auth 的 Organization 插件提供了完整的多租户能力——类似 GitHub Organizations 或 Slack Workspaces:
// 创建组织
async function createOrganization(name: string, slug: string) {
const { data, error } = await organization.create({
name, // "我的团队"
slug, // "my-team" — URL 友好的唯一标识
logo: "https://example.com/logo.png",
});
return data;
}
// 邀请成员
async function inviteMember(email: string, role: "admin" | "member") {
const { data, error } = await organization.inviteMember({
email,
role,
organizationId: "当前组织ID",
});
// 系统自动发送邀请邮件
}
// 在 Server Component 中获取当前组织上下文
// app/dashboard/layout.tsx
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) redirect("/login");
// 获取用户所属的组织列表
const organizations = await auth.api.listOrganizations({
headers: await headers(),
});
return (
<div className="flex">
<aside className="w-64 border-r">
<h2 className="p-4 font-bold">我的组织</h2>
{organizations?.map((org) => (
<a
key={org.id}
href={`/org/${org.slug}`}
className="block p-4 hover:bg-gray-100"
>
{org.name}
</a>
))}
</aside>
<main className="flex-1">{children}</main>
</div>
);
}
⚠️ 四、生产部署避坑指南
常见问题与解决方案
在生产环境中使用 Better Auth,以下几个问题需要特别注意:
❌ 错误写法 — 环境变量配置不当:
# .env — 危险配置
BETTER_AUTH_URL=http://localhost:3000 # 硬编码 localhost
BETTER_AUTH_SECRET=my-secret-key # 太短且可预测
✅ 正确写法 — 生产级配置:
# .env.production — 安全配置
BETTER_AUTH_URL=https://myapp.com
BETTER_AUTH_SECRET=$(openssl rand -base64 48)
GITHUB_CLIENT_ID=Ov23liXXXXXXXXXXXXXXX
GITHUB_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
DATABASE_URL=postgresql://user:pass@db:5432/myapp?sslmode=require
数据库迁移注意事项:
# Better Auth 会自动管理数据库表结构
# 但建议在生产环境使用 Drizzle 迁移而不是 auto-migrate
npx @better-auth/cli migrate
# 或者使用 Drizzle 手动管理
npx drizzle-kit generate
npx drizzle-kit push
Session 安全加固
// lib/auth.ts — 生产环境安全配置
export const auth = betterAuth({
// ... 基础配置同上
session: {
expiresIn: 60 * 60 * 24 * 7,
updateAge: 60 * 60 * 24,
cookieCache: {
enabled: true,
maxAge: 5 * 60,
},
},
advanced: {
// 使用安全的 Cookie 配置
defaultCookieAttributes: {
secure: true, // 仅 HTTPS
httpOnly: true, // 防止 XSS 读取
sameSite: "lax", // 防止 CSRF
partitioned: true, // Chrome 第三方 Cookie 淘汰后的最佳实践
},
// IP 地址绑定 — 防止 Session 劫持
ipAddress: {
ipAddressHeaders: ["x-forwarded-for", "x-real-ip"],
},
// User-Agent 指纹 — 增加 Session 安全性
userAgent: {
userAgentHeaders: ["user-agent"],
},
},
// 邮件验证回调
emailVerification: {
sendVerificationEmail: async ({ user, url, token }) => {
// 使用 Resend / Nodemailer / AWS SES 发送验证邮件
await sendEmail({
to: user.email,
subject: "验证你的邮箱",
html: `<a href="${url}">点击验证</a>`,
});
},
sendOnSignUp: true, // 注册时自动发送验证邮件
},
});
性能优化建议
| 优化项 | 配置 | 效果 |
|---|---|---|
| Cookie 缓存 | cookieCache.enabled: true |
减少 80% 数据库 Session 查询 |
| Session 过期策略 | updateAge: 86400 (每天) |
平衡安全性与性能 |
| Rate Limiting | window: 60, max: 10 |
防止暴力破解 |
| 数据库索引 | 确保 session.token 有索引 |
查询从 O(n) 到 O(1) |
| 连接池 | 配置 Drizzle 连接池 | 避免连接耗尽 |
⚡ **关键结论:**Better Auth 的 Cookie 缓存是生产环境必须开启的功能。它将 Session 验证从「每次请求查数据库」变为「每 5 分钟查一次数据库」,在高并发场景下能显著降低数据库压力。
💡 五、与 Auth.js 的深度对比
对于正在纠结选择哪个认证框架的开发者,这里给出关键维度的对比:
| 维度 | Better Auth | Auth.js (NextAuth v5) |
|---|---|---|
| 类型安全 | ✅ 端到端类型推导 | ⚠️ 部分类型支持 |
| Passkey 支持 | ✅ 原生插件 | ⚠️ 需要第三方适配器 |
| 2FA 支持 | ✅ 原生插件(TOTP) | ❌ 需要自己实现 |
| 多租户组织 | ✅ 原生插件 | ❌ 不支持 |
| 管理 API | ✅ 完整的 Admin API | ❌ 需要自己实现 |
| 数据库适配 | ✅ Drizzle/Prisma/Kysely/原生 | ✅ Prisma/Drizzle/原生 |
| 框架支持 | ✅ 全框架覆盖 | ⚠️ 主要面向 Next.js |
| 社区生态 | 📈 快速增长 | 🏆 成熟稳定 |
| 学习曲线 | 📊 中等(文档完善) | 📊 中等(文档完善) |
| 推荐场景 | 新项目、需要完整功能 | 已有 Auth.js 项目、仅需基础登录 |
💡 **我的建议:**如果你是 2026 年启动的新项目,且需要 Passkey、2FA 或多租户功能,强烈推荐 Better Auth。它的插件架构让你可以按需加载功能,不会引入不必要的复杂性。如果你的项目已经在用 Auth.js 且只需要基础的邮箱+OAuth 登录,没有必要迁移。
📋 总结
Better Auth 代表了 TypeScript 认证框架的新范式——插件化、类型安全、功能完整。它的核心价值在于:
- ✅ 用一个框架覆盖所有认证场景,不再需要拼凑多个库
- ✅ 插件架构让功能扩展变得优雅,核心保持精简
- ✅ 完全自托管,数据完全在你自己的数据库中
- ✅ 与主流全栈框架深度集成,开发体验一流
🔧 相关工具推荐
- Drizzle ORM:配合 Better Auth 的最佳数据库适配器
- Resend:用于发送验证邮件和邀请邮件
- Upstash Redis:作为 Better Auth 的 Rate Limit 存储后端
- jsjson.com 在线工具:JSON 格式化、Base64 编解码、JWT 解析等开发者常用工具
📌 **下一步行动:**访问 better-auth.com 查看完整文档,在 30 分钟内为你的项目接入完整的认证系统。