Better Auth 实战指南:TypeScript 全栈认证框架从入门到生产

深入解析 Better Auth 认证框架,涵盖邮箱登录、OAuth 社交登录、Passkey 无密码认证、双因素验证、多租户组织管理等核心功能,附完整代码示例与生产部署最佳实践。

安全与密码 2026-06-10 12 分钟

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 分钟内为你的项目接入完整的认证系统。

📚 相关文章