身份认证
Nuxt 模板中的 Better Auth 登录、OAuth 与路由保护。
身份认证基于 Better Auth 端到端集成:邮箱/密码 (强制验证)、密码重置、GitHub/Google OAuth(含账号关联)、会话,以及 admin 插件。
它提供了什么
- 邮箱/密码注册,强制邮箱验证,验证后自动登录。
- 通过邮件重置密码。
- GitHub 与 Google OAuth,并启用账号关联。
- admin 插件(角色、封禁字段)与受角色保护的管理员用户 API。
- 两层路由保护:客户端中间件负责体验,API 校验负责权威性。
关键文件
| 文件 | 作用 |
|---|---|
server/utils/auth.ts | Better Auth 服务端配置(createBetterAuth / useServerAuth) |
server/api/auth/[...all].ts | 挂载 Better Auth 路由的 catch-all 处理器 |
server/middleware/auth.ts | 每个请求附加 event.context.auth;导出 requireAuth / getAuthContext |
app/composables/useAuth.ts | Vue 客户端(createAuthClient);导出 signIn/signUp/signOut/useSession/getSession |
app/middleware/auth.ts | 客户端路由守卫 —— 无会话时跳转登录 |
app/middleware/admin.ts | 客户端路由守卫 —— 要求 role === "admin" |
app/pages/auth/* | login、register、register-success、confirm、forgot-password、reset-password(使用 auth 布局) |
服务端配置
server/utils/auth.ts 中的 createBetterAuth() 将 Drizzle 适配器
(provider: "pg")接入认证 schema,并配置:
baseURL与trustedOrigins来自runtimeConfig.public.baseUrl,再加上NUXT_AUTH_EXTRA_TRUSTED_ORIGINS中逗号分隔的来源。emailAndPassword:启用,requireEmailVerification: true,sendResetPassword→ 用forgotPassword模板调用sendEmail。emailVerification:sendOnSignUp: true、autoSignInAfterVerification: true,sendVerificationEmail→ 用verifyEmail模板调用sendEmail。socialProviders:GitHub 与 Google,来自各自的NUXT_*_CLIENT_ID/SECRET。account.accountLinking.enabled: true。plugins: [admin()]。
验证与重置邮件由 Vue Email 模板经 sendEmail(...) 渲染 —— 没有内联 HTML
路径。未设置 NUXT_RESEND_API_KEY 时,这些邮件无法发送,新账号也无法完成验证。
参见 邮件。
客户端配置
app/composables/useAuth.ts 使用 better-auth/vue 的 createAuthClient 创建
客户端,注册 adminClient 插件,并重新导出 signIn、signUp、signOut、
useSession 与 getSession。认证页面在 auth 布局下使用它们。
OAuth 提供商
设置某提供商的一对变量即可启用(参见 环境变量):
- GitHub ——
NUXT_GITHUB_CLIENT_ID、NUXT_GITHUB_CLIENT_SECRET - Google ——
NUXT_GOOGLE_CLIENT_ID、NUXT_GOOGLE_CLIENT_SECRET
在提供商控制台中使用回调 URL
<NUXT_PUBLIC_BASE_URL>/api/auth/callback/<provider>。当某提供商未配置凭据时,
在登录/注册页隐藏或移除其按钮。
两层路由保护
保护采用纵深防御:客户端守卫服务于体验,API 处理函数才是权威。
客户端路由守卫(体验)
app/middleware/auth.ts 与 app/middleware/admin.ts 仅在客户端运行(SSR 时提前
返回)。它们读取已缓存的响应式 useSession() store —— 不会每次导航都请求服务端:
auth→ 无会话时跳转/auth/login。admin→ 未登录跳转/auth/login;用户role不是admin时跳转/dashboard。
页面通过 definePageMeta 启用:
// app/pages/(dashboard)/dashboard.vue
definePageMeta({ middleware: "auth" });
// app/pages/(dashboard)/admin/users.vue
definePageMeta({ middleware: ["auth", "admin"] });API 校验(权威)
server/middleware/auth.ts 在每个请求上运行,只负责附加
event.context.auth。真正的强制校验在每个处理函数中。例如
server/api/admin/users.get.ts 调用 requireAuth(event)(缺失则 401),并在
currentUser.role !== "admin" 时返回 403:
const { user: currentUser } = requireAuth(event);
if (currentUser.role !== "admin") {
throw createError({ statusCode: 403, statusMessage: "Forbidden - Admin role required" });
}只有在 definePageMeta 中声明了 middleware 的页面才受保护,而且仅有页面守卫并
不等于安全。任何取数路由都必须自行做 requireAuth(以及角色)校验 —— 客户端守卫
可被绕过。
管理员角色
角色来自 admin 插件;role 与 banned/banReason/banExpires 字段位于 user
表上。将某用户的 role 设为 "admin"(例如在 Drizzle Studio 中)即可提升权限 ——
随后 ["auth", "admin"] 页面与管理员用户 API 对其开放。
验证
- 在
/auth/register注册;收到验证邮件。 - 验证邮箱;自动登录。
- 退出后用邮箱/密码重新登录。
- 任一已配置提供商的 OAuth 登录可用。
- 收到密码重置邮件并完成重置。
- 未登录访问
/dashboard会跳转/auth/login。 - 非管理员访问
/admin/users会跳转/dashboard,且/api/admin/users调用返回 403。
