身份认证

TanStack Start 中的 Better Auth 邮箱、OAuth、会话、角色与路由保护。

身份认证通过 Better Auth 端到端集成:邮箱/密码注册与强制验证、密码重置、 GitHub 和 Google OAuth、账号关联、会话以及管理员插件。

提供的能力

  • 邮箱/密码注册,并在注册时发送验证邮件。
  • 密码重置邮件与基于 token 的重置表单。
  • GitHub 和 Google 社交登录与账号关联。
  • 客户端会话 hook 与权威的服务端会话检查。
  • 带语言环境跳转的登录和管理员路由守卫。
  • 管理员角色、封禁字段,以及检查角色的用户 API。

重要文件

文件作用
src/lib/auth.tsBetter Auth 服务端配置与延迟 auth 代理
src/lib/auth-client.tsReact 客户端、admin client 插件,以及 signInsignUpsignOutuseSession
src/lib/auth-guard.ts服务端支持的 requireAuthrequireAdmin 路由守卫
src/routes/api/auth/$.ts挂载到 /api/auth/$ 的 GET/POST 通配处理器
src/routes/{-$locale}/auth/*登录、注册、成功、忘记密码与重置密码页面
src/lib/db/schema/auth.tsBetter Auth 用户、会话、账号和验证表
src/routes/api/admin/users.ts检查会话与角色的管理员用户 API

服务端设置与 Workers 运行时

src/lib/auth.ts 通过 D1 认证 schema 配置 drizzleAdapter(db, { provider: "sqlite" }),并启用邮箱/密码、强制验证、 密码重置、GitHub/Google、账号关联、admin()tanstackStartCookies()

导出的 auth 是延迟 Proxy。Better Auth 在请求期间首次访问属性时才创建, 此时 D1 绑定与 Worker 密钥才可用。不要把它替换成在模块求值期间捕获绑定或 密钥的顶层 betterAuth(...) 单例。

通配路由将两种方法转发给 Better Auth:

export const Route = createFileRoute("/api/auth/$")({
	server: {
		handlers: {
			GET: ({ request }) => auth.handler(request),
			POST: ({ request }) => auth.handler(request),
		},
	},
});

邮箱/密码与邮件投递

requireEmailVerification: truesendOnSignUp: true 将验证设为默认 登录流程的一部分。验证与忘记密码回调通过 sendEmail() 调用 React Email 模板。

测试完整流程前,请设置 RESEND_API_KEY 与有效发件人。Resend 配置不可用时, 注册可能进入成功页,但验证或重置消息不会送达。详见 邮件

客户端会话

src/lib/auth-client.ts 使用 better-auth/reactcreateAuthClient, 加入 adminClient() 与推断的 role 字段,并导出:

export const { signIn, signUp, signOut, useSession } = authClient;

useSession() 适合渲染用户菜单等客户端 UI,但不是授权边界。服务端函数与 API 处理器必须在返回受保护数据或修改状态前调用 auth.api.getSession({ headers })

OAuth 服务商

环境变量设置每个已启用服务商的 ID 与密钥,并在服务商控制台注册:

https://app.example.com/api/auth/callback/github
https://app.example.com/api/auth/callback/google

本地 OAuth 应用使用 http://localhost:3000 作为 origin,并保持 BETTER_AUTH_URL 与其一致。未配置某服务商时,请在 src/components/auth/OAuthButtons.tsx 删除或隐藏它的按钮;模板默认显示两个。

OAuthButtons 将保留语言环境的 dashboard URL 作为 callbackURL。登录表单 也会保留路由守卫传来的 callbackUrl,认证后用户会回到原受保护页面。

路由保护

这是 TanStack Start 对应 Next.js proxy.ts 的机制。src/lib/auth-guard.ts 定义 getSessionAccess createServerFn,在服务端读取请求 headers 并调用 auth.api.getSession(...)

  • requireAuth 保护 /dashboard/admin/setting/account (也包括带语言前缀的形式),未登录用户会跳到本地化登录页并携带 callbackUrl
  • requireAdmin 额外检查 session.user.role === "admin";其他已登录用户会被 重定向到本地化 dashboard。
  • _dashboard 布局设置 beforeLoad: requireAuth;管理员用户页设置 beforeLoad: requireAdmin

导航守卫改善路由体验,但敏感 API 必须再次检查会话。例如 /api/admin/users 在无会话时返回 401,服务端角色不是 admin 时返回 403。 所有新增受保护处理器都应遵循相同模式。

角色与管理员访问

管理员插件向用户 schema 增加 rolebannedbanReasonbanExpires。 默认角色是 user。请通过可审计的管理流程或受控数据库操作提升账号,绝不能 信任浏览器提交的角色。

导航是否显示只属于界面表现;requireAdmin 与每个管理员 API 自身的角色检查 才是授权边界。

验证

  • 使用邮箱/密码注册,并确认 D1 创建用户记录。
  • 打开验证链接并登录。
  • 请求密码重置,并通过邮件 token 完成重置。
  • 使用每个已配置 OAuth 服务商登录,并确认账号关联。
  • 退出登录,确认会话 UI 更新。
  • 未登录访问 /dashboard,确认跳转到 /auth/login
  • 完成登录,确认 callbackUrl 返回原受保护页面。
  • /zh/dashboard 重复流程,确认登录过程保留 /zh
  • 确认非管理员无法打开 /admin/users 或调用 /api/admin/users

相关页面

On this page