认证与账号
注册、登录、验证邮箱、社交登录、2FA 和账号中心。根据产品需求完成配置后即可使用,无需重新实现认证系统。
Saavo 模板内置了完整的账号系统,涵盖注册、登录、邮箱验证、找回密码、社交登录、两步验证、账号中心和管理员身份等常见能力。
开发自己的产品时,通常不需要重新实现这些功能:根据产品需求调整认证策略,配置好邮件、OAuth 等依赖服务,然后在页面、API 和前端组件中接入并校验用户身份即可。
已有功能
模板初始化后,以下认证能力开箱即用:
| 能力 | 默认入口 | 默认状态 |
|---|---|---|
| 注册 | /auth/signup | 已启用 |
| 登录 | /auth/login | 已启用 |
| 找回密码 | /auth/forgot-password | 已启用 |
| 邮箱验证 | /auth/verify-email | 注册后发送验证邮件,但默认不强制验证 |
| Google 登录 | 登录页 | 配置密钥后启用 |
| GitHub 登录 | 登录页 | 配置密钥后启用 |
| 两步验证 | /account | 用户可自行开启 |
| 账号中心 | /account | 已登录用户可访问 |
| 管理后台 | /dashboard | 管理员可访问 |
除上述功能外,Saavo 还实现了 Session 管理、登录弹窗、恢复码、高敏操作二次验证,以及其他认证相关的安全限制。
当基于 Saavo 模板开发自己的产品时,不需要再创建另一套登录状态,也不需要在业务代码里自己解析 Cookie 或直接操作 Session 数据,业务代码应基于 Saavo 提供的认证结果来二次开发。
先体验注册和登录
修改认证配置之前,建议先用默认配置完整走一遍认证流程,快速了解模板已提供的能力。
启动开发服务器后,打开首页,右上角会显示登录按钮:

点击登录按钮,可以通过登录弹窗直接完成登录。Saavo 也提供了独立的登录页面:

还没有账号的话,可以进入注册页面,填写邮箱和密码完成注册:

默认情况下,Saavo 会发送邮箱验证邮件,但不会强制用户验证,未验证也能继续使用产品。
本地开发时,模板不会真正发送邮件,而是把模拟邮件输出到开发服务器控制台。你可以直接从日志里找到验证码,例如:
[DEV EMAIL PREVIEW] {
recipients: [ 'saavo@live.com' ],
subject: 'Welcome to Saavo Starter',
text: 'Hello, saavo,\n' +
'\n' +
'Thank you for registering with Saavo Starter! To ensure the security of your account, we need to verify your email address.\n' +
'\n' +
'Your verification code is 2RBFH7IA\n' +
'\n' +
'If you did not sign up for a Saavo Starter account, please disregard this email.\n' +
'\n' +
'If you have any questions, feel free to contact our support team.\n' +
'\n' +
'Best regards,\n' +
'The Saavo Starter Team',
html: ''
}用上述邮件中的验证码即可完成邮箱验证。如果没有开启强制验证,也可以先跳过:

登录后,通过右上角头像进入用户中心:

用户中心(/account)已包含个人信息、安全设置(密码、邮箱、两步验证)、已购产品和支付记录等功能。
除了上述功能,模板还为管理员提供了独立的控制台页面:

管理员邮箱在 adminEmails 中配置,只有完成邮箱匹配和邮箱验证的用户才能获得管理员身份,访问 /dashboard。
提示
开发人员通常不需要重新实现这些页面,而是根据产品需求调整认证策略,让业务直接复用已有的认证状态。
配置用户认证
默认配置以快速跑通模板为目标。正式开发产品时,建议至少确认以下几个关键选项。
是否强制验证邮箱
默认情况下,用户注册后会收到验证邮件,即使未完成验证也可以登录。
产品正式对外服务后,尤其是资源消耗较高、需要防范批量注册或对账号真实性有要求的场景,建议开启强制邮箱验证:
emailVerification: {
defaultRequired: true,
}开启后,未完成邮箱验证的用户访问受保护页面时,会被引导去完成邮箱验证。
注册后是否发送验证邮件,由另一个配置决定:
emailVerification: {
sendRegisterEmail: true,
}也就是说,“是否发送验证邮件”和“是否必须完成邮箱验证”是两个独立的设置。
邮箱验证默认使用验证码方式:
emailVerification: {
sendVerifyType: 'code',
}如果想让用户直接点击邮件中的链接完成验证,改为:
emailVerification: {
sendVerifyType: 'link',
}注意事项
开启强制邮箱验证前,请先确保生产环境的邮件服务能正常使用,否则新注册用户将无法通过邮箱验证继续使用产品。
选择登录方式
邮箱和密码登录默认已启用。
此外,Saavo 还支持 Google 和 GitHub 登录。第三方登录根据当前环境变量按需启动,没有配置对应的 OAuth 密钥时,登录入口不会显示。
如果你的 SaaS 产品主要面向普通消费者,那么开启 Google 登录通常能降低注册门槛;面向开发者时,再根据目标用户决定是否提供 GitHub 登录,如果你需要开启其它第三方登录,例如 Apple 登录,则需要参考相关文档自行实现。
使用 Google 登录时,还可以通过下面的配置开启 Google One Tap:
enableGoogleOneTap: trueClient ID、Client Secret 和 Callback URL 的具体配置见后文“配置认证依赖的服务”。
是否强制使用双重验证
Saavo 已经提供基于验证器 App 的两步验证和恢复码。
默认配置为:
twoFactorAuth: {
defaultRequired: false,
}2FA 功能是可选的,上述选项表示所有用户默认不启用,但用户可以随时在 /account 的安全设置里自行开启。

这种方式适合大多数 SaaS 产品:需要额外安全保护的用户可以主动开启,其他用户也不必为此付出额外的注册和使用成本。
如果产品有更高的安全要求,可以设置:
twoFactorAuth: {
defaultRequired: true,
}开启后,用户需要先完成两步验证设置才能使用产品。
恢复码数量可以通过下面的配置调整:
twoFactorAuth: {
recoveryCodeCount: 5,
}另外,初始化产品时记得修改 twoFactorAuth.issuer 配置:
twoFactorAuth: {
issuer: 'Acme',
}issuer 会显示在 Google Authenticator、1Password 等验证器 App 中,应改成自己的产品名称。
设置登录后的跳转页面
模板默认在注册、登录和退出后返回首页。
真实的 SaaS 产品中,用户登录后通常应直接进入应用。例如产品主界面在 /app:
ui: {
redirectTo: {
afterSignup: '/app',
afterLogin: '/app',
afterLogout: '/',
},
}调整后的流程:
注册成功 → /app
登录成功 → /app
退出登录 → /建议尽早修改这部分配置,避免用户登录后仍被带回模板默认首页。
选择登录弹窗或登录页面
Saavo 默认启用登录弹窗功能,用户可以直接通过右上角的登录按钮打开弹窗,也可能在业务操作中被动触发显示登录界面。
例如未登录用户点击购买按钮时,可以在当前页面完成登录,然后继续原来的付款流程,而不需要跳转到登录页面。
如果不需要登录弹窗,可以关闭 ui.useLoginModal 配置:
ui: {
useLoginModal: false,
}关闭后,需要登录时统一跳转到完整登录页面。
两种方式各有适用场景:用户经常在操作过程中被要求登录时,弹窗更自然;登录本身就是明确的独立步骤时,完整登录页更简单。
添加管理员
Saavo 通过 config/base.ts 中的 adminEmails 设置管理员:
adminEmails: ['admin@saavo.dev'],用该邮箱注册账号并完成邮箱验证后,即可获得管理员身份并访问:
/dashboardadminEmails 不是绕过认证的超级账号。管理员首先是普通用户,只有已完成验证的邮箱与配置中的地址完全匹配,才会获得管理员身份。
建议产品初始化后尽早创建自己的管理员账号,并确认能正常进入管理后台。
准备认证服务
认证逻辑已内置在模板中,但邮件、OAuth、Turnstile 等功能仍依赖外部服务和环境变量。
SAAS_SECRET
每个项目都需要配置 SAAS_SECRET 环境变量,避免重复使用相同的值:
SAAS_SECRET=...Session 和其他敏感数据的加密都依赖它,建议每个项目都独立设置。
邮件服务
以下认证流程都需要发送邮件:
- 注册后的邮箱验证
- 重新发送验证码
- 找回密码
- 修改邮箱
- 其他需要邮件确认的安全操作
本地开发时,Saavo 会直接使用控制台输出的模拟邮件,不会真正发送邮件。
正式部署后,需要配置真正用于发送邮件的服务。支持的邮件服务和配置方式见:
如果开启了强制邮箱验证,应先确认邮件服务能正常发送邮件。
Google 和 GitHub 登录
如果需要 Google 登录,在 .env 中配置:
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...Google OAuth 应用中的 Redirect URI 应设置为:
https://your-domain.com/api/auth/oauth2/google如果需要 GitHub 登录:
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...对应的 Redirect URI 为:
https://your-domain.com/api/auth/oauth2/github没有配置对应密钥时,该登录方式不会显示。
修改 .env 后需要重启开发服务器。
完整的 Google 登录配置流程见:
Turnstile 人机验证
Saavo 支持用 Cloudflare Turnstile 为注册、登录等认证页面增加人机验证。
默认不是每次访问都显示 Turnstile,而是根据认证操作的风险计数触发:
useTurnstile: {
threshold: 2,
interval: '1h',
}其中:
threshold表示触发人机验证的风险计数阈值。interval表示风险计数的有效期。
按默认配置,对应认证操作的风险计数达到 2 时,就会要求人机验证。参数错误、认证失败和部分检查事件会增加计数,成功事件可以降低计数,因此不能把它理解为“刷新页面超过两次”。

需要更早触发验证时,可以调低阈值。不需要 Turnstile 时,也可以直接设置:
useTurnstile: false启用 Turnstile 时需要配置:
CLOUDFLARE_TURNSTILE_SITE_KEY=...
CLOUDFLARE_TURNSTILE_SECRET_KEY=...本地开发可以使用 Cloudflare 提供的测试密钥,生产环境应使用自己站点申请的正式密钥。
在业务中验证用户身份
配置完成后,下一步就是让业务代码读取当前用户的认证状态。
基本原则是:
不要在业务代码中自己读取 Cookie,也不要直接查询或修改 Session 表。
Saavo 已通过 Guard 统一处理登录状态、邮箱验证、两步验证等认证条件,业务代码直接用认证结果即可。
保护页面
很多 SaaS 页面只允许登录用户访问,例如账号中心、工作台或用户自己的项目页面。
服务端渲染页面可以在页面 Handler 中调用 authenticatedGuard:
const accountPageHandlers = createLayoutHandlers(
createLayoutRenderer('page'),
(c: Ctx) => {
const locale = c.var.getLocale();
const guardResult = authenticatedGuard(c);
if (!guardResult.success) {
return c.redirect(
getFullPath(guardResult.details?.redirectUrl as string, locale),
);
}
const { authContext } = guardResult.data;
return <AccountPage c={c} />;
},
);
export default accountPageHandlers;authenticatedGuard 不只是判断用户有没有 Session。
它会根据当前认证策略自动处理不同的认证状态:
未登录
↓
登录页面
需要验证邮箱,但尚未验证
↓
邮箱验证
要求 2FA,但尚未完成
↓
两步验证流程
所有认证要求均满足
↓
继续执行业务页面因此,业务页面不需要重复实现登录、邮箱验证和 2FA 判断。
如果页面还需要管理员权限、角色或 Capability,在认证通过后再做相应检查即可。
完整示例见:
保护 API
API 使用同一套认证守卫,只是失败时返回 JSON 而不是重定向:
const guardResult = authenticatedGuard(c);
if (!guardResult.success) {
return c.json(
guardResult,
guardResult.code === gResultCode.authLoginRequired ? 401 : 403,
);
}
const { authContext } = guardResult.data;这样页面和 API 的认证规则保持一致。
以后把邮箱验证或 2FA 从可选改为强制时,也无需逐个修改业务接口的认证逻辑。
使用 authContext 处理用户业务
认证成功后,从 authContext 对象中可以获取当前请求的认证信息,它包含当前用户和 Session 的信息,是后续业务逻辑的身份入口,也是后续权限控制的基础。
例如,一个用户正在创建自己的项目:
POST /api/projects服务端需要知道这个项目属于谁。
这时应该使用认证上下文中的当前用户身份,而不是直接信任前端提交的 userId。
类似的业务还有:
- 创建属于当前用户的数据
- 查询当前用户的项目
- 修改自己的资源
- 判断当前角色
- 检查 Entitlement
- 检查 Capability
- 判断当前用户是否允许执行某个操作
整个流程通常是:
请求
↓
authenticatedGuard()
↓
authContext
↓
角色 / 权益 / 能力
↓
业务逻辑认证负责确定“当前是谁”,Role、Entitlement 和 Capability 则进一步决定“当前用户可以做什么”。
在前端读取当前用户
除了服务端认证,前端组件有时也需要知道当前用户是否已登录。
Saavo 使用 MPA 渲染方案,跨组件共享的前端状态需要通过 Store 进行管理。用户状态目前基于 nanostores 实现,你可以通过 useCurrentUser 获取:
import { setLoginModalOpen } from '@/stores/login-modal';
import { useCurrentUser } from '@/stores/user';
const { isLoggedIn } = useCurrentUser();
if (!isLoggedIn) {
setLoginModalOpen(true);
return;
}这个例子会在用户未登录时打开登录弹窗。
其他组件修改用户信息后,也可以通过 setCurrentUser 更新共享状态。
需要注意,前端的 isLoggedIn 主要用于控制界面行为,例如显示登录按钮、打开登录弹窗或隐藏某些入口。
数据访问和权限控制仍必须在服务端用 Guard、Role、Entitlement 或 Capability 检查,前端状态不能作为安全边界。
扩展账号设置
/account 已经覆盖了大多数账号级别的设置,无需重复实现。
适合继续放在 /account 的内容包括:
- 用户资料
- 邮箱
- 密码
- 两步验证
- 恢复码
- 已购产品
- 支付和账单信息
这些功能都属于用户账号本身。
而属于具体 SaaS 业务的设置,应放在业务自己的页面里。例如一个 AI 产品可能还需要:
默认 AI 模型
生成参数
工作区设置
通知设置
团队配置这些内容由业务模块自己实现,而不是继续堆到 /account 中。
一个简单的判断方式是:
与“用户是谁”有关的设置放在
/account,与“产品如何工作”有关的设置放在业务页面。
上线检查
认证是产品上线前必须完整验证的基础功能,建议至少检查以下流程:
- 注册、邮箱验证、登录和退出流程正常。
- 找回密码可以正常完成。
-
/account中的账号功能可以正常使用。 - 已启用的 Google / GitHub 第三方登录可以正常完成。
- 如启用 2FA,可以完成开启、验证和恢复码流程。
- 未登录用户无法访问受保护页面和 API。
- 普通用户无法访问
/dashboard。 - 管理员可以正常访问
/dashboard。 - 生产环境使用独立的
SAAS_SECRET。 - 生产环境邮件可以正常发送。
- OAuth 的 Redirect URI 已经切换为生产域名。
- Turnstile 等认证安全配置已经使用生产环境密钥。
建议用一个全新注册的普通账号和一个管理员账号完整测试,不要只用开发期间一直存在的老测试账号。
常见问题
接下来
根据接下来要开发的功能,可以继续阅读:
- 想启用 Google 登录 → 接入 Google 登录
- 想创建登录后页面 → 登录后页面
- 想创建管理员页面 → 管理员页面
- 想配置认证邮件 → 邮件
- 想限制用户可以使用哪些功能 → 付费才开放的功能
- 想查认证接口和返回格式 → API
大多数产品走完本章后,就不需要再修改认证系统本身。
接下来直接基于当前登录用户,接入产品真正的业务能力即可。