身份认证
TanStack Start 中的 Better Auth 邮箱、OAuth、会话、角色与路由保护。
身份认证通过 Better Auth 端到端集成:邮箱/密码注册与强制验证、密码重置、 GitHub 和 Google OAuth、账号关联、会话以及管理员插件。
提供的能力
- 邮箱/密码注册,并在注册时发送验证邮件。
- 密码重置邮件与基于 token 的重置表单。
- GitHub 和 Google 社交登录与账号关联。
- 客户端会话 hook 与权威的服务端会话检查。
- 带语言环境跳转的登录和管理员路由守卫。
- 管理员角色、封禁字段,以及检查角色的用户 API。
重要文件
| 文件 | 作用 |
|---|---|
src/lib/auth.ts | Better Auth 服务端配置与延迟 auth 代理 |
src/lib/auth-client.ts | React 客户端、admin client 插件,以及 signIn、signUp、signOut、useSession |
src/lib/auth-guard.ts | 服务端支持的 requireAuth 与 requireAdmin 路由守卫 |
src/routes/api/auth/$.ts | 挂载到 /api/auth/$ 的 GET/POST 通配处理器 |
src/routes/{-$locale}/auth/* | 登录、注册、成功、忘记密码与重置密码页面 |
src/lib/db/schema/auth.ts | Better 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: true 和 sendOnSignUp: true 将验证设为默认
登录流程的一部分。验证与忘记密码回调通过 sendEmail() 调用 React Email
模板。
测试完整流程前,请设置 RESEND_API_KEY 与有效发件人。Resend 配置不可用时,
注册可能进入成功页,但验证或重置消息不会送达。详见
邮件。
客户端会话
src/lib/auth-client.ts 使用 better-auth/react 的 createAuthClient,
加入 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 增加 role、banned、banReason 与 banExpires。
默认角色是 user。请通过可审计的管理流程或受控数据库操作提升账号,绝不能
信任浏览器提交的角色。
导航是否显示只属于界面表现;requireAdmin 与每个管理员 API 自身的角色检查
才是授权边界。
验证
- 使用邮箱/密码注册,并确认 D1 创建用户记录。
- 打开验证链接并登录。
- 请求密码重置,并通过邮件 token 完成重置。
- 使用每个已配置 OAuth 服务商登录,并确认账号关联。
- 退出登录,确认会话 UI 更新。
- 未登录访问
/dashboard,确认跳转到/auth/login。 - 完成登录,确认
callbackUrl返回原受保护页面。 - 从
/zh/dashboard重复流程,确认登录过程保留/zh。 - 确认非管理员无法打开
/admin/users或调用/api/admin/users。
