Better Auth 完全指南:构建企业级认证系统
前言
在构建现代 Web 应用时,认证系统是最重要也是最复杂的部分之一。Better Auth 作为一个 TypeScript 优先的认证框架,提供了一套完整、类型安全、易扩展的解决方案。本文将深入探讨 Better Auth 的核心概念、实现方案,以及如何构建一个功能完备的企业内部管理系统。
Better Auth 是什么?
Better Auth 是一个框架无关的 TypeScript 认证和授权框架。它的设计理念是:
- 🔒 类型安全:端到端的 TypeScript 支持
- 🚀 高性能:轻量级设计,最小运行时开销
- 🔧 易扩展:强大的插件系统
- 📦 功能完备:开箱即用的认证功能
- 🎯 框架无关:支持 Next.js、Nuxt、Express 等
核心工作原理
1. 架构设计
Better Auth 采用了模块化的架构设计:
┌─────────────────────────────────────────────────┐
│ 应用层 │
├─────────────────────────────────────────────────┤
│ Better Auth Core │
│ ┌─────────┐ ┌─────────┐ ┌──────────────┐ │
│ │ Session │ │ User │ │ Account │ │
│ │ Manager │ │ Manager │ │ Linking │ │
│ └─────────┘ └─────────┘ └──────────────┘ │
├─────────────────────────────────────────────────┤
│ 插件系统 │
│ ┌─────────┐ ┌─────────┐ ┌──────────────┐ │
│ │ Magic │ │ OAuth │ │ RBAC │ │
│ │ Link │ │ Plugins │ │ (Admin) │ │
│ └─────────┘ └─────────┘ └──────────────┘ │
├─────────────────────────────────────────────────┤
│ 数据库适配器 │
│ (Drizzle ORM / Prisma / etc) │
└─────────────────────────────────────────────────┘
2. 认证流程
Better Auth 的认证流程设计非常灵活:
typescript
// 1. 初始化 Better Auth
import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg"
}),
// 启用邮箱密码认证
emailAndPassword: {
enabled: true
},
// 配置社交登录
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}
}
})
多种登录方式实现
1. Google 一键登录
Better Auth 内置了对主流社交平台的支持。实现 Google 登录非常简单:
服务端配置:
typescript
// auth.ts
export const auth = betterAuth({
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
// 可选:请求额外权限
scope: ["email", "profile", "openid"]
}
}
})
客户端实现:
typescript
// auth-client.ts
import { createAuthClient } from "better-auth/client"
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL
})
// 登录组件
export function GoogleLoginButton() {
const handleGoogleLogin = async () => {
await authClient.signIn.social({
provider: "google",
callbackURL: "/dashboard",
// 新用户跳转到欢迎页
newUserCallbackURL: "/welcome",
// 错误处理
errorCallbackURL: "/login?error=google"
})
}
return (
<button onClick={handleGoogleLogin}>
使用 Google 账号登录
</button>
)
}
2. 邮箱密码登录
传统的邮箱密码登录仍然是企业系统的主流选择:
服务端配置:
typescript
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
// 自定义密码要求
minPasswordLength: 8,
maxPasswordLength: 128,
// 密码加密配置
password: {
hash: async (password) => {
// 使用 bcrypt 或其他加密方法
return await bcrypt.hash(password, 10)
},
verify: async (password, hash) => {
return await bcrypt.compare(password, hash)
}
}
}
})
客户端实现:
typescript
// 注册
const signUp = async (email: string, password: string, name: string) => {
const { data, error } = await authClient.signUp.email({
email,
password,
name,
// 注册后自动登录
callbackURL: "/dashboard"
})
if (error) {
console.error("注册失败:", error)
}
return data
}
// 登录
const signIn = async (email: string, password: string) => {
const { data, error } = await authClient.signIn.email({
email,
password,
rememberMe: true // 记住登录状态
})
return data
}
3. Magic Link 无密码登录
Magic Link 是一种安全便捷的无密码登录方式:
服务端配置:
typescript
import { magicLink } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
magicLink({
sendMagicLink: async ({ email, token, url }, request) => {
// 使用你的邮件服务发送魔法链接
await sendEmail({
to: email,
subject: "登录链接",
html: `
<h1>点击下方链接登录</h1>
<a href="${url}">登录</a>
<p>链接将在 10 分钟后失效</p>
<p>验证码:${token}</p>
`
})
},
// 可选配置
expiresIn: 60 * 10, // 10 分钟有效期
disableSignUp: false, // 允许新用户通过 Magic Link 注册
})
]
})
客户端实现:
typescript
import { magicLinkClient } from "better-auth/client/plugins"
export const authClient = createAuthClient({
plugins: [magicLinkClient()]
})
// 发送 Magic Link
const sendMagicLink = async (email: string) => {
const { data, error } = await authClient.signIn.magicLink({
email,
callbackURL: "/dashboard",
newUserCallbackURL: "/onboarding"
})
if (data) {
alert("登录链接已发送到您的邮箱")
}
}
4. 账户关联(统一用户身份)
Better Auth 的账户关联功能允许用户使用多种方式登录同一个账户:
启用账户关联:
typescript
export const auth = betterAuth({
account: {
accountLinking: {
enabled: true,
// 信任的提供商(自动关联相同邮箱)
trustedProviders: ["google", "github"],
// 允许不同邮箱关联
allowDifferentEmails: true
}
}
})
关联流程实现:
typescript
// 在用户设置页面
export function AccountLinking() {
const { data: session } = useSession()
const linkGoogleAccount = async () => {
await authClient.linkSocial({
provider: "google",
callbackURL: "/settings/accounts"
})
}
const unlinkAccount = async (providerId: string, accountId: string) => {
await authClient.unlinkAccount({
providerId,
accountId
})
}
return (
<div>
<h2>关联账户</h2>
{session?.user?.accounts?.map(account => (
<div key={account.id}>
<span>{account.provider}</span>
<button onClick={() => unlinkAccount(account.provider, account.id)}>
解除关联
</button>
</div>
))}
<button onClick={linkGoogleAccount}>
关联 Google 账户
</button>
</div>
)
}
角色权限控制(RBAC)
Better Auth 通过 Admin 和 Access Control 插件提供了强大的角色权限管理功能:
1. 基础权限系统设计
定义权限声明(Statement):
typescript
// permissions.ts
import { createAccessControl } from "better-auth/plugins/access"
// 定义资源和操作
export const statement = {
// 用户管理
user: ["create", "read", "update", "delete", "ban"],
// 部门管理
department: ["create", "read", "update", "delete", "assign"],
// 项目管理
project: ["create", "read", "update", "delete", "share"],
// 报表权限
report: ["view", "export", "create"],
// 系统设置
system: ["config", "backup", "restore"]
} as const
// 创建访问控制器
export const ac = createAccessControl(statement)
定义角色:
typescript
// 普通员工
export const staffRole = ac.newRole({
user: ["read"],
project: ["read", "create"],
report: ["view"]
})
// 部门经理
export const managerRole = ac.newRole({
user: ["read", "update"],
department: ["read", "assign"],
project: ["create", "read", "update", "share"],
report: ["view", "export", "create"]
})
// 系统管理员
export const adminRole = ac.newRole({
user: ["create", "read", "update", "delete", "ban"],
department: ["create", "read", "update", "delete", "assign"],
project: ["create", "read", "update", "delete", "share"],
report: ["view", "export", "create"],
system: ["config", "backup", "restore"]
})
2. 集成到 Better Auth
typescript
// auth.ts
import { betterAuth } from "better-auth"
import { admin } from "better-auth/plugins"
import { ac, staffRole, managerRole, adminRole } from "./permissions"
export const auth = betterAuth({
plugins: [
admin({
ac,
roles: {
staff: staffRole,
manager: managerRole,
admin: adminRole
},
// 默认角色
defaultRole: "staff"
})
]
})
3. 权限检查实现
服务端权限检查:
typescript
// api/projects/route.ts
import { auth } from "@/lib/auth"
export async function POST(request: Request) {
const session = await auth.api.getSession({
headers: request.headers
})
if (!session) {
return new Response("Unauthorized", { status: 401 })
}
// 检查用户是否有创建项目的权限
const hasPermission = await auth.api.hasPermission({
body: {
permission: {
project: ["create"]
}
},
headers: request.headers
})
if (!hasPermission) {
return new Response("Forbidden", { status: 403 })
}
// 创建项目...
}
客户端权限检查:
typescript
// hooks/usePermission.ts
import { adminClient } from "better-auth/client/plugins"
export const authClient = createAuthClient({
plugins: [
adminClient({
ac,
roles: { staff: staffRole, manager: managerRole, admin: adminRole }
})
]
})
// React Hook
export function usePermission() {
const { data: session } = useSession()
const hasPermission = useCallback((permissions: any) => {
if (!session) return false
return authClient.admin.hasPermission({ permissions })
}, [session])
const checkRole = useCallback((role: string) => {
return session?.user?.role === role
}, [session])
return { hasPermission, checkRole, role: session?.user?.role }
}
// 使用示例
function ProjectList() {
const { hasPermission } = usePermission()
return (
<div>
{hasPermission({ project: ["create"] }) && (
<button>创建项目</button>
)}
</div>
)
}
企业内部管理系统设计
基于 Better Auth,我们可以构建一个功能完备的企业内部管理系统。以下是完整的设计方案:
1. 数据库模型设计
typescript
// schema.ts
import { pgTable, text, timestamp, uuid, boolean, integer, jsonb } from "drizzle-orm/pg-core"
import { relations } from "drizzle-orm"
// Better Auth 基础表(用户、会话、账户)
export const user = pgTable("user", {
id: text("id").primaryKey(),
email: text("email").notNull().unique(),
emailVerified: boolean("emailVerified").notNull().default(false),
name: text("name").notNull(),
image: text("image"),
role: text("role").notNull().default("staff"), // staff, manager, admin
departmentId: uuid("department_id").references(() => department.id),
position: text("position"), // 职位
phone: text("phone"),
isActive: boolean("is_active").default(true),
joinedAt: timestamp("joined_at").defaultNow(),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
metadata: jsonb("metadata"), // 扩展字段
})
// 部门表
export const department = pgTable("department", {
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
code: text("code").unique(), // 部门编码
parentId: uuid("parent_id").references(() => department.id),
managerId: text("manager_id").references(() => user.id),
description: text("description"),
isActive: boolean("is_active").default(true),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
})
// 模块权限表
export const module = pgTable("module", {
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
code: text("code").unique().notNull(), // 模块编码,如 'crm', 'finance'
description: text("description"),
icon: text("icon"),
path: text("path"), // 前端路由
parentId: uuid("parent_id").references(() => module.id),
order: integer("order").default(0),
isActive: boolean("is_active").default(true),
})
// 角色模块权限关联表
export const roleModulePermission = pgTable("role_module_permission", {
id: uuid("id").primaryKey().defaultRandom(),
role: text("role").notNull(), // 角色名
moduleId: uuid("module_id").references(() => module.id),
permissions: jsonb("permissions"), // ["read", "write", "delete"]
createdAt: timestamp("created_at").defaultNow(),
})
// 审计日志表
export const auditLog = pgTable("audit_log", {
id: uuid("id").primaryKey().defaultRandom(),
userId: text("user_id").references(() => user.id),
action: text("action").notNull(), // 'login', 'create', 'update', 'delete'
resource: text("resource").notNull(), // 'user', 'department', 'module'
resourceId: text("resource_id"),
changes: jsonb("changes"), // 变更详情
ip: text("ip"),
userAgent: text("user_agent"),
createdAt: timestamp("created_at").defaultNow(),
})
2. 认证配置增强
typescript
// auth.ts
import { betterAuth } from "better-auth"
import { admin, twoFactor, magicLink, rateLimit } from "better-auth/plugins"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg"
}),
// 基础配置
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
// 社交登录(企业微信、钉钉等)
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
// 限制企业域名
mapProfileToUser: (profile) => {
if (!profile.email?.endsWith("@company.com")) {
throw new Error("仅允许公司邮箱登录")
}
return {
email: profile.email,
name: profile.name,
image: profile.picture,
}
}
}
},
// 插件配置
plugins: [
// 管理员功能
admin({
ac,
roles: {
staff: staffRole,
manager: managerRole,
admin: adminRole,
},
defaultRole: "staff"
}),
// 双因素认证
twoFactor({
issuer: "Company OA",
// 强制管理员启用 2FA
requireTwoFactor: async (user) => {
return user.role === "admin"
}
}),
// Magic Link
magicLink({
sendMagicLink: async ({ email, url, token }) => {
// 发送邮件
},
expiresIn: 60 * 15, // 15分钟
}),
// 速率限制
rateLimit({
window: 60, // 60秒
max: 5, // 最多5次
storage: "memory", // 或使用 Redis
})
],
// 会话配置
session: {
expiresIn: 60 * 60 * 24 * 7, // 7天
updateAge: 60 * 60 * 24, // 每天更新
cookieName: "company-oa-session",
},
// Hooks
hooks: {
after: [
{
matcher: () => true,
handler: async (ctx) => {
// 记录审计日志
if (ctx.request && ctx.body) {
await db.insert(auditLog).values({
userId: ctx.context.session?.userId,
action: ctx.request.method,
resource: ctx.request.url,
ip: ctx.request.headers.get("x-forwarded-for"),
userAgent: ctx.request.headers.get("user-agent"),
})
}
}
}
],
user: {
create: {
before: async (user) => {
// 自动分配部门
if (!user.departmentId && user.email) {
const dept = await guessUserDepartment(user.email)
user.departmentId = dept?.id
}
return user
}
}
}
}
})
3. 权限管理系统实现
typescript
// permissions/index.ts
import { createAccessControl } from "better-auth/plugins/access"
// 定义所有权限点
export const permissions = {
// 用户管理
user: {
view: "查看用户",
create: "创建用户",
update: "更新用户",
delete: "删除用户",
ban: "禁用用户",
resetPassword: "重置密码",
},
// 部门管理
department: {
view: "查看部门",
create: "创建部门",
update: "更新部门",
delete: "删除部门",
assign: "分配成员",
},
// 模块管理
module: {
crm: {
customer: ["view", "create", "update", "delete"],
order: ["view", "create", "update", "delete", "approve"],
},
finance: {
invoice: ["view", "create", "update", "delete", "approve"],
payment: ["view", "create", "update", "approve"],
report: ["view", "export"],
},
inventory: {
product: ["view", "create", "update", "delete"],
stock: ["view", "update", "transfer"],
purchase: ["view", "create", "update", "approve"],
}
}
}
// 创建访问控制
export const ac = createAccessControl({
user: Object.keys(permissions.user),
department: Object.keys(permissions.department),
customer: permissions.module.crm.customer,
order: permissions.module.crm.order,
invoice: permissions.module.finance.invoice,
payment: permissions.module.finance.payment,
product: permissions.module.inventory.product,
stock: permissions.module.inventory.stock,
} as const)
// 定义角色权限矩阵
export const rolePermissions = {
// 普通员工
staff: {
user: ["view"],
department: ["view"],
customer: ["view", "create", "update"],
order: ["view", "create"],
product: ["view"],
},
// 部门经理
manager: {
...rolePermissions.staff,
user: ["view", "update"],
department: ["view", "assign"],
order: ["view", "create", "update", "approve"],
invoice: ["view", "create"],
payment: ["view"],
stock: ["view", "update"],
},
// 管理员(所有权限)
admin: Object.keys(permissions).reduce((acc, key) => {
acc[key] = "all"
return acc
}, {})
}
4. 前端权限控制组件
typescript
// components/PermissionGuard.tsx
import { usePermission } from "@/hooks/usePermission"
import { ReactNode } from "react"
interface PermissionGuardProps {
resource: string
action: string | string[]
fallback?: ReactNode
children: ReactNode
}
export function PermissionGuard({
resource,
action,
fallback = null,
children
}: PermissionGuardProps) {
const { hasPermission } = usePermission()
const actions = Array.isArray(action) ? action : [action]
const canAccess = hasPermission({ [resource]: actions })
return canAccess ? <>{children}</> : <>{fallback}</>
}
// 使用示例
<PermissionGuard resource="user" action="create">
<button>创建用户</button>
</PermissionGuard>
// 路由保护
export function ProtectedRoute({
resource,
action,
children
}: {
resource: string
action: string
children: ReactNode
}) {
const { hasPermission } = usePermission()
const router = useRouter()
useEffect(() => {
if (!hasPermission({ [resource]: [action] })) {
router.push("/403")
}
}, [])
return hasPermission({ [resource]: [action] }) ? children : null
}
5. API 权限中间件
typescript
// middleware/auth.ts
import { auth } from "@/lib/auth"
import { NextRequest, NextResponse } from "next/server"
export function withAuth(
handler: (req: NextRequest, session: any) => Promise<NextResponse>,
options?: {
resource?: string
action?: string
}
) {
return async (req: NextRequest) => {
// 获取会话
const session = await auth.api.getSession({
headers: req.headers
})
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
// 检查权限
if (options?.resource && options?.action) {
const hasPermission = await auth.api.hasPermission({
body: {
permission: {
[options.resource]: [options.action]
}
},
headers: req.headers
})
if (!hasPermission) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
}
}
return handler(req, session)
}
}
// 使用示例
export const POST = withAuth(
async (req, session) => {
// 创建用户逻辑
},
{ resource: "user", action: "create" }
)
最佳实践和安全建议
1. 安全配置
typescript
// 1. 限制登录尝试
import { rateLimit } from "better-auth/plugins"
auth.use(rateLimit({
window: 15 * 60, // 15分钟
max: 5, // 最多5次
storage: "redis", // 使用 Redis 存储
}))
// 2. 会话安全
session: {
cookieName: "company-oa-session",
cookieOptions: {
httpOnly: true,
secure: true, // HTTPS only
sameSite: "lax",
}
}
// 3. 密码策略
emailAndPassword: {
minPasswordLength: 10,
requireUppercase: true,
requireNumbers: true,
requireSpecialChars: true,
}
2. 监控和审计
typescript
// 审计日志 Hook
hooks: {
after: [
{
matcher: (ctx) => {
// 只记录重要操作
const importantPaths = ["/user", "/department", "/role"]
return importantPaths.some(path => ctx.request?.url?.includes(path))
},
handler: async (ctx) => {
await logAuditEvent({
userId: ctx.context.session?.userId,
action: ctx.body?.action || ctx.request?.method,
resource: ctx.request?.url,
changes: ctx.body,
timestamp: new Date(),
})
}
}
]
}
3. 性能优化
typescript
// 1. 缓存权限检查结果
const permissionCache = new Map()
export function getCachedPermission(userId: string, permission: string) {
const key = `${userId}:${permission}`
if (permissionCache.has(key)) {
return permissionCache.get(key)
}
const result = checkPermission(userId, permission)
permissionCache.set(key, result)
// 5分钟后过期
setTimeout(() => permissionCache.delete(key), 5 * 60 * 1000)
return result
}
// 2. 预加载用户权限
export async function preloadUserPermissions(userId: string) {
const user = await db.query.user.findFirst({
where: eq(user.id, userId),
with: {
role: true,
department: true,
}
})
// 缓存用户完整权限
return cacheUserPermissions(user)
}
总结
Better Auth 提供了一个强大而灵活的认证授权框架,特别适合构建企业级应用:
核心优势
- 类型安全:完整的 TypeScript 支持,减少运行时错误
- 功能完备:支持多种认证方式,满足企业各种需求
- 易于扩展:插件系统让功能扩展变得简单
- 安全可靠:内置安全最佳实践,如 CSRF 保护、会话管理等
- 性能优秀:轻量级设计,最小运行时开销
适用场景
- 企业内部管理系统
- SaaS 应用
- 需要复杂权限控制的系统
- 多租户应用
- 需要合规审计的系统
实施建议
- 从简单开始:先实现基础认证,再逐步添加高级功能
- 权限设计先行:在开发前仔细设计权限模型
- 注重用户体验:提供多种登录方式,但不要让用户困惑
- 安全第一:启用所有安全功能,定期审查权限配置
- 监控和审计:记录所有重要操作,便于问题追踪
通过 Better Auth,我们可以快速构建一个安全、可靠、易维护的企业级认证系统,让开发者专注于业务逻辑的实现。
相关资源
希望这篇指南能帮助你深入理解 Better Auth,构建出色的认证系统!