身份认证

Nuxt 模板中的 Better Auth 登录、OAuth 与路由保护。

身份认证基于 Better Auth 端到端集成:邮箱/密码 (强制验证)、密码重置、GitHub/Google OAuth(含账号关联)、会话,以及 admin 插件。

它提供了什么

  • 邮箱/密码注册,强制邮箱验证,验证后自动登录。
  • 通过邮件重置密码。
  • GitHub 与 Google OAuth,并启用账号关联。
  • admin 插件(角色、封禁字段)与受角色保护的管理员用户 API。
  • 两层路由保护:客户端中间件负责体验,API 校验负责权威性。

关键文件

文件作用
server/utils/auth.tsBetter Auth 服务端配置(createBetterAuth / useServerAuth
server/api/auth/[...all].ts挂载 Better Auth 路由的 catch-all 处理器
server/middleware/auth.ts每个请求附加 event.context.auth;导出 requireAuth / getAuthContext
app/composables/useAuth.tsVue 客户端(createAuthClient);导出 signIn/signUp/signOut/useSession/getSession
app/middleware/auth.ts客户端路由守卫 —— 无会话时跳转登录
app/middleware/admin.ts客户端路由守卫 —— 要求 role === "admin"
app/pages/auth/*loginregisterregister-successconfirmforgot-passwordreset-password(使用 auth 布局)

服务端配置

server/utils/auth.ts 中的 createBetterAuth() 将 Drizzle 适配器 (provider: "pg")接入认证 schema,并配置:

  • baseURLtrustedOrigins 来自 runtimeConfig.public.baseUrl,再加上 NUXT_AUTH_EXTRA_TRUSTED_ORIGINS 中逗号分隔的来源。
  • emailAndPassword:启用,requireEmailVerification: truesendResetPassword → 用 forgotPassword 模板调用 sendEmail
  • emailVerificationsendOnSignUp: trueautoSignInAfterVerification: truesendVerificationEmail → 用 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/vuecreateAuthClient 创建 客户端,注册 adminClient 插件,并重新导出 signInsignUpsignOutuseSessiongetSession。认证页面在 auth 布局下使用它们。

OAuth 提供商

设置某提供商的一对变量即可启用(参见 环境变量):

  • GitHub —— NUXT_GITHUB_CLIENT_IDNUXT_GITHUB_CLIENT_SECRET
  • Google —— NUXT_GOOGLE_CLIENT_IDNUXT_GOOGLE_CLIENT_SECRET

在提供商控制台中使用回调 URL <NUXT_PUBLIC_BASE_URL>/api/auth/callback/<provider>。当某提供商未配置凭据时, 在登录/注册页隐藏或移除其按钮。

两层路由保护

保护采用纵深防御:客户端守卫服务于体验,API 处理函数才是权威。

客户端路由守卫(体验)

app/middleware/auth.tsapp/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 插件;rolebanned/banReason/banExpires 字段位于 user 表上。将某用户的 role 设为 "admin"(例如在 Drizzle Studio 中)即可提升权限 —— 随后 ["auth", "admin"] 页面与管理员用户 API 对其开放。

验证

  • /auth/register 注册;收到验证邮件。
  • 验证邮箱;自动登录。
  • 退出后用邮箱/密码重新登录。
  • 任一已配置提供商的 OAuth 登录可用。
  • 收到密码重置邮件并完成重置。
  • 未登录访问 /dashboard 会跳转 /auth/login
  • 非管理员访问 /admin/users 会跳转 /dashboard,且 /api/admin/users 调用返回 403。

相关页面

On this page