Frameworks
authentication
BetterAuth 认证系统
BetterAuth 是一个现代化的 TypeScript 认证库,为 vibetake 提供了完整的用户认证解决方案。本文档将详细介绍如何配置和使用 BetterAuth 认证系统。
概述
vibetake 集成了 BetterAuth 认证系统,提供以下功能:
- 📧 邮箱密码认证
- 🔐 会话管理
- 🛡️ 安全的密码处理
- 📱 客户端和服务端认证状态同步
- 🗄️ 与 Drizzle ORM 的无缝集成
系统架构
graph TB
A[客户端] --> B[Auth Client]
B --> C[API Routes]
C --> D[Better Auth Core]
D --> E[Drizzle Adapter]
E --> F[数据库]
subgraph "认证流程"
G[登录/注册] --> H[验证凭据]
H --> I[创建会话]
I --> J[返回用户信息]
end
快速开始
1. 环境配置
首先,确保在 .env
文件中配置了必要的环境变量:
# Better Auth 配置
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_BETTER_AUTH_URL=http://localhost:3000
# 数据库配置
DATABASE_URL=postgresql://postgres:password@localhost:5432/your_database
2. 基本使用
在客户端组件中使用认证功能:
"use client";
import { useSession, signIn, signOut } from "@/services/userauth/auth-client";
export function AuthExample() {
const { data: session, isPending } = useSession();
if (isPending) {
return <div>加载中...</div>;
}
if (session) {
return (
<div>
<p>欢迎, {session.user.name}!</p>
<button onClick={() => signOut()}>退出登录</button>
</div>
);
}
return (
<button
onClick={() =>
signIn.email({
email: "user@example.com",
password: "password",
})
}
>
登录
</button>
);
}
核心配置
服务端配置
BetterAuth 的核心配置位于 src/services/userauth/auth.ts
:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/services/database/client";
import * as schema from "@/services/database/schema";
export const auth = betterAuth({
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
database: drizzleAdapter(db, {
provider: "sqlite", // 或 "postgresql"
schema,
}),
emailAndPassword: {
enabled: true,
},
});
客户端配置
客户端配置位于 src/services/userauth/auth-client.ts
:
"use client";
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000",
});
export const { signUp, signIn, signOut, useSession } = authClient;
数据库模式
BetterAuth 需要以下数据库表结构(位于 src/services/database/schema.ts
):
用户表 (user)
export const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name"),
email: text("email").notNull(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at")
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
updatedAt: timestamp("updated_at")
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
});
账户表 (account)
export const account = pgTable("account", {
id: text("id").primaryKey(),
userId: text("user_id")
.notNull()
.references(() => user.id),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
password: text("password"), // 用于邮箱密码认证
// ... 其他字段
});
会话表 (session)
export const session = pgTable("session", {
id: text("id").primaryKey(),
userId: text("user_id")
.notNull()
.references(() => user.id),
token: text("token").notNull(),
expiresAt: timestamp("expires_at").notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
// ... 其他字段
});
用户注册流程
1. 注册表单组件
"use client";
import { useState } from "react";
import { signUp } from "@/services/userauth/auth-client";
import { toast } from "sonner";
export function RegisterForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const result = await signUp.email({
email,
password,
name,
callbackURL: "/login",
});
if (result.error) {
toast.error(result.error.message);
} else {
toast.success("注册成功!请登录。");
}
} catch (error) {
toast.error("注册失败,请重试。");
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="姓名"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<input
type="email"
placeholder="邮箱"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit" disabled={loading}>
{loading ? "注册中..." : "注册"}
</button>
</form>
);
}
2. 注册流程说明
- 用户输入信息:姓名、邮箱、密码
- 客户端验证:基本的表单验证
- 发送注册请求:调用
signUp.email()
方法 - 服务端处理:
- 验证邮箱格式和密码强度
- 检查邮箱是否已存在
- 创建用户记录
- 生成加密密码
- 返回结果:成功或错误信息
用户登录流程
1. 登录表单组件
"use client";
import { useState } from "react";
import { signIn } from "@/services/userauth/auth-client";
export function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
await signIn.email({
email,
password,
callbackURL: "/",
});
} catch (err: any) {
setError(err?.message ?? "登录失败");
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="邮箱"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit" disabled={loading}>
{loading ? "登录中..." : "登录"}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
2. 登录流程说明
- 用户输入凭据:邮箱和密码
- 客户端验证:基本的表单验证
- 发送登录请求:调用
signIn.email()
方法 - 服务端验证:
- 查找用户记录
- 验证密码
- 创建会话
- 设置认证 Cookie
- 重定向:登录成功后跳转到指定页面
会话管理
1. 会话状态检查
"use client";
import { useSession } from "@/services/userauth/auth-client";
export function SessionExample() {
const { data: session, isPending, error } = useSession();
if (isPending) {
return <div>检查登录状态...</div>;
}
if (error) {
return <div>会话错误: {error.message}</div>;
}
if (session) {
return (
<div>
<h2>用户信息</h2>
<p>ID: {session.user.id}</p>
<p>姓名: {session.user.name}</p>
<p>邮箱: {session.user.email}</p>
<p>邮箱已验证: {session.user.emailVerified ? "是" : "否"}</p>
</div>
);
}
return <div>未登录</div>;
}
2. 服务端会话验证
import { auth } from "@/services/userauth/auth";
import { headers } from "next/headers";
export async function getServerSession() {
const session = await auth.api.getSession({
headers: headers(),
});
return session;
}
// 在 Server Component 中使用
export default async function ProtectedPage() {
const session = await getServerSession();
if (!session) {
redirect("/login");
}
return (
<div>
<h1>受保护的页面</h1>
<p>欢迎, {session.user.name}!</p>
</div>
);
}
权限管理
1. 路由保护
创建一个高阶组件来保护需要认证的页面:
"use client";
import { useSession } from "@/services/userauth/auth-client";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
export function withAuth<P extends object>(Component: React.ComponentType<P>) {
return function AuthenticatedComponent(props: P) {
const { data: session, isPending } = useSession();
const router = useRouter();
useEffect(() => {
if (!isPending && !session) {
router.push("/login");
}
}, [session, isPending, router]);
if (isPending) {
return <div>验证中...</div>;
}
if (!session) {
return null;
}
return <Component {...props} />;
};
}
// 使用示例
const ProtectedPage = withAuth(() => {
return <div>这是受保护的页面</div>;
});
2. 中间件保护
创建 middleware.ts
文件来保护 API 路由:
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/services/userauth/auth";
export async function middleware(request: NextRequest) {
// 检查是否是受保护的路由
if (request.nextUrl.pathname.startsWith("/api/protected")) {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
return NextResponse.json({ error: "未授权访问" }, { status: 401 });
}
}
return NextResponse.next();
}
export const config = {
matcher: ["/api/protected/:path*"],
};
API 路由集成
1. 认证 API 路由
BetterAuth 自动处理认证相关的 API 路由,位于 src/app/api/auth/[...all]/route.ts
:
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/services/userauth/auth";
export const { GET, POST } = toNextJsHandler(auth);
这个路由处理以下端点:
POST /api/auth/sign-in/email
- 邮箱登录POST /api/auth/sign-up/email
- 邮箱注册POST /api/auth/sign-out
- 退出登录GET /api/auth/session
- 获取会话信息
2. 自定义 API 路由
创建需要认证的 API 路由:
// app/api/protected/profile/route.ts
import { auth } from "@/services/userauth/auth";
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
return NextResponse.json({ error: "未授权访问" }, { status: 401 });
}
// 返回用户信息
return NextResponse.json({
user: session.user,
});
}
错误处理
1. 常见错误类型
interface AuthError {
message: string;
code: string;
}
// 常见错误代码
const AUTH_ERRORS = {
INVALID_CREDENTIALS: "凭据无效",
USER_NOT_FOUND: "用户不存在",
EMAIL_ALREADY_EXISTS: "邮箱已存在",
WEAK_PASSWORD: "密码强度不够",
SESSION_EXPIRED: "会话已过期",
} as const;
2. 错误处理示例
"use client";
import { useState } from "react";
import { signIn } from "@/services/userauth/auth-client";
export function LoginWithErrorHandling() {
const [error, setError] = useState<string | null>(null);
const handleLogin = async (email: string, password: string) => {
try {
setError(null);
await signIn.email({ email, password });
} catch (err: any) {
// 处理不同类型的错误
switch (err.code) {
case "INVALID_CREDENTIALS":
setError("邮箱或密码错误");
break;
case "USER_NOT_FOUND":
setError("用户不存在,请先注册");
break;
default:
setError("登录失败,请重试");
}
}
};
return (
<div>
{error && <div className="error-message">{error}</div>}
{/* 登录表单 */}
</div>
);
}
最佳实践
1. 安全配置
// 生产环境配置
export const auth = betterAuth({
baseURL: process.env.BETTER_AUTH_URL,
database: drizzleAdapter(db, {
provider: "postgresql",
schema,
}),
emailAndPassword: {
enabled: true,
minPasswordLength: 8,
maxPasswordLength: 128,
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 天
updateAge: 60 * 60 * 24, // 1 天
},
advanced: {
crossSubDomainCookies: {
enabled: true,
domain: ".yourdomain.com",
},
},
});
2. 密码安全
- 使用强密码策略(最少 8 位,包含大小写字母、数字和特殊字符)
- 实施密码重试限制
- 考虑添加双因素认证
3. 会话管理
- 设置合理的会话过期时间
- 实施会话刷新机制
- 在敏感操作前重新验证
4. 监控和日志
// 添加认证事件监听
auth.on("session.created", (session) => {
console.log("用户登录:", session.user.email);
});
auth.on("session.deleted", (session) => {
console.log("用户退出:", session.user.email);
});
故障排除
常见问题
-
会话不持久
- 检查 Cookie 设置
- 确认域名配置正确
-
登录后立即退出
- 检查数据库连接
- 验证会话表结构
-
CORS 错误
- 配置正确的
baseURL
- 检查客户端和服务端 URL 一致性
- 配置正确的
调试技巧
// 启用调试模式
export const auth = betterAuth({
// ... 其他配置
logger: {
level: "debug",
},
});
扩展功能
1. 社交登录
虽然当前模板主要使用邮箱密码认证,但 BetterAuth 支持多种社交登录:
// 添加 Google 登录
export const auth = betterAuth({
// ... 其他配置
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
});
2. 邮箱验证
export const auth = betterAuth({
// ... 其他配置
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
emailVerification: {
sendOnSignUp: true,
autoSignInAfterVerification: true,
},
});
通过本文档,你应该能够完全理解和使用 vibetake 中的 BetterAuth 认证系统。如有问题,请参考 BetterAuth 官方文档 或在项目仓库中提出 Issue。