Coolify 中的 Laravel Fortify 认证体系:配置项、可覆盖 Action 与关键端点实战解析
本篇以 Coolify 仓库中的开发技能文档 .agents/skills/fortify-development/SKILL.md 为主体,系统讲解 Laravel Fortify 作为"无前端(headless)认证后端"在 Coolify 中的落地方式:如何通过 config/fortify.php 启用功能特性、如何在 app/Actions/Fortify/ 下覆盖用户创建与密码逻辑、如何用 FortifyServiceProvider 注册视图回调与响应契约,以及登录限流在 RouteServiceProvider 中的真实实现。读完后你可以完整掌握 Fortify 的端点清单、各功能的启用条件,并具备在自有 Laravel 项目中复用同一套认证架构的能力。
什么是 Fortify:无前端认证后端
Fortify 是一个 headless 的认证后端,它为 Laravel 应用注册认证所需的全部路由与控制器(登录、注册、密码重置、邮箱验证、两步验证、Passkey 等),但不绑定任何前端视图——界面由应用自己通过"视图回调"提供。这一设计使 Fortify 既能驱动传统 Blade 页面,也能驱动 Livewire、SPA 或纯 API 客户端。
Coolify 正是这一模式的典型使用者:
- 认证路由与控制器由 Fortify 包注册(登录、登出、注册、忘记密码、两步验证挑战等);
- 视图层由 app/Providers/FortifyServiceProvider.php 中的
Fortify::loginView()、Fortify::registerView()等回调绑定到 Coolify 自有的 Blade 页面(如auth.login、auth.register); - 业务逻辑(用户创建、密码重置、资料更新)集中在 app/Actions/Fortify/ 目录下的四个类中,通过契约(Contracts)注入。
从源码结构看,Fortify 的扩展点可归纳为五类:路由/端点(包内注册)、Actions(可覆盖的业务逻辑)、Config(特性开关与全局选项)、Contracts(可覆盖的响应类,如 LoginResponse、LogoutResponse、RegisterResponse)、视图回调(在 FortifyServiceProvider::boot() 中设置)。
核心配置:config/fortify.php 逐项解读
Coolify 的 config/fortify.php 展示了 Fortify 的全部关键配置段,实际取值如下:
| 配置项 | Coolify 取值 | 作用 |
|---|---|---|
guard |
web |
Fortify 认证所用的 guard,必须与 config/auth.php 中的某个 guard 对应;SPA 会话认证模式下必须为 web |
passwords |
users |
密码重置使用的 broker,对应 auth.php 中的 password broker |
username / email |
均为 email |
"用户名"字段所用的模型属性;也决定忘记密码/重置请求中预期的字段名 |
home |
RouteServiceProvider::HOME |
登录或重置成功后的跳转路径 |
prefix / domain |
'' / null |
所有 Fortify 路由的前缀与子域名,Coolify 使用无前缀默认 |
middleware |
['web'] |
Fortify 路由挂载的中间件组 |
limiters |
login、two-factor、forgot-password |
登录、两步验证挑战、忘记密码使用的限流器名称 |
views |
true |
是否启用返回视图的路由;SPA 项目可设为 false 只保留 JSON 接口 |
features |
见下文 | 功能特性开关数组 |
features 数组是 Fortify 的功能总开关,可用的特性有:
Features::registration()— 用户注册;Features::resetPasswords()— 通过邮箱重置密码;Features::emailVerification()— 邮箱验证(要求User实现MustVerifyEmail接口);Features::updateProfileInformation()— 更新个人资料;Features::updatePasswords()— 修改密码;Features::twoFactorAuthentication()— 两步验证(TOTP,带二维码与恢复码),可附加选项;Features::passkeys()— 基于 WebAuthn 的 Passkey 无密码认证。
Coolify 当前启用了其中五项(config/fortify.php):
'features' => [
Features::registration(),
Features::resetPasswords(),
// Features::emailVerification(),
Features::updateProfileInformation(),
Features::updatePasswords(),
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
// 'window' => 0,
]),
],
两点值得注意:
- 邮箱验证特性被注释掉了。从源码看,Coolify 没有让
User实现MustVerifyEmail(app/Models/User.php 仅实现了SendsEmail接口),而是用自建流程替代:自托管部署时用户创建后直接调用markEmailAsVerified(),云版本则派发SendVerificationEmailJob(见下文CreateNewUser分析)。 - 两步验证启用了
confirm => true与confirmPassword => true:前者要求用户在启用 2FA 后通过"确认"步骤二次认证,后者在敏感操作前要求再次输入密码。confirm选项还会联动数据库迁移——见下文 2FA 章节。
关键端点清单
Fortify 注册的完整端点(按功能特性启用与否而定)如下,这是排查认证问题时最常用的一张表:
| 功能 | 方法 | 端点 |
|---|---|---|
| 登录 | POST | /login |
| 登出 | POST | /logout |
| 注册 | POST | /register |
| 请求密码重置 | POST | /forgot-password |
| 执行密码重置 | POST | /reset-password |
| 邮箱验证通知 | GET | /email/verify |
| 重发验证邮件 | POST | /email/verification-notification |
| 密码确认 | POST | /user/confirm-password |
| 启用 2FA | POST | /user/two-factor-authentication |
| 确认 2FA | POST | /user/confirmed-two-factor-authentication |
| 2FA 挑战 | POST | /two-factor-challenge |
| 获取二维码 | GET | /user/two-factor-qr-code |
| 恢复码 | GET/POST | /user/two-factor-recovery-codes |
| Passkey 登录选项 | GET | /passkeys/login/options |
| Passkey 登录 | POST | /passkeys/login |
| Passkey 确认选项 | GET | /passkeys/confirm/options |
| Passkey 确认 | POST | /passkeys/confirm |
| Passkey 选项 | GET | /user/passkeys/options |
| 注册 Passkey | POST | /user/passkeys |
| 删除 Passkey | DELETE | /user/passkeys/{passkey} |
开发时可以用 php artisan route:list(或技能文档中提到的 list-routes --only-vendor 方式)按 Fortify 控制器筛选,快速确认哪些端点在当前特性配置下实际可用。
Actions 层:四个可覆盖的业务逻辑类
Fortify 把认证流程中的业务动作抽象成契约(Contracts),应用侧在 app/Actions/Fortify/ 提供实现并在 Provider 中注入。Coolify 实现了四个:
CreateNewUser:注册逻辑的重灾区
app/Actions/Fortify/CreateNewUser.php 实现 CreatesNewUsers 契约,它包含了三块 Coolify 定制逻辑:
(1)注册开关与双重限流。 注册前先检查实例级开关,再执行自定义限流:
private const REGISTRATION_IP_MAX_ATTEMPTS = 3;
private const REGISTRATION_IP_DECAY_SECONDS = 600;
private const REGISTRATION_EMAIL_IDENTITY_MAX_ATTEMPTS = 3;
private const REGISTRATION_EMAIL_IDENTITY_DECAY_SECONDS = 3600;
is_registration_enabled为 false 时直接abort(403);- 按 IP 维度(
registration:ip:{sha1(ip)})10 分钟内最多 3 次,超限abort(429); - 按归一化邮箱身份维度(
registration:email-identity:{sha1(...)})1 小时内最多 3 次。normalize_email_identity()辅助函数可剥离+别名等变体,防止"同一个人换邮箱后缀"绕过限制。
(2)首个用户即 root 用户。 当数据库中尚无任何用户时,新用户以 id => 0 强制填充(forceFill),挂载到预先由 seeder 创建好的 Root Team(Team::find(0))并赋予 owner 角色;创建完成后立即关闭注册开关(is_registration_enabled = false),保证自托管实例只有第一个注册用户。
(3)邮箱验证策略分流。 非首个用户的处理因部署形态而异:
if (isCloud()) {
SendVerificationEmailJob::dispatch($user);
} else {
$user->markEmailAsVerified();
}
验证邮件由 User::sendVerificationEmail() 发出,链接使用 URL::temporarySignedRoute('verify.verify', ...) 生成的临时签名路由,有效期取自 config('auth.verification.expire', 60)(分钟)。
其余三个 Action
- ResetUserPassword.php(实现
ResetsUserPasswords):校验新密码满足Password::defaults()且confirmed,写入后调用$user->deleteAllSessions()撤销所有已签发会话——这正是"重置密码后其他设备掉线"的来源; - UpdateUserPassword.php(实现
UpdatesUserPasswords):要求current_password通过current_password:web校验(即对照webguard 的当前密码),新密码同样要求Password::defaults()+confirmed,错误信息使用updatePassword校验 bag; - UpdateUserProfileInformation.php(实现
UpdatesUserProfileInformation):校验name与email(Rule::unique('users')->ignore($user->id));若用户实现了MustVerifyEmail且邮箱变更,则清空email_verified_at并重新发送验证通知,否则直接落库。
FortifyServiceProvider:视图回调与响应契约
app/Providers/FortifyServiceProvider.php 是 Coolify 定制 Fortify 行为的核心文件,分为 register() 与 boot() 两部分。
register():覆盖 RegisterResponse 契约
Fortify 的认证响应通过 Laravel\Fortify\Contracts\ 下的契约类定义,可在容器中用自定义实例替换。Coolify 覆盖了 RegisterResponse:
$this->app->instance(RegisterResponse::class, new class implements RegisterResponse
{
public function toResponse($request)
{
// First user (root) will be redirected to /settings instead of / on registration.
if ($request->user()->currentTeam->id === 0) {
return redirect()->route('settings.index');
}
return redirect(RouteServiceProvider::HOME);
}
});
即:首个用户(root,属于 id = 0 的 Root Team)注册完成后被导向 /settings 完成实例初始化,其余用户回首页。同理,LoginResponse、LogoutResponse 等契约也可按同样方式覆盖以实现自定义跳转。
boot():视图回调与自定义认证
boot() 中依次完成:
- 注入 Actions:
Fortify::createUsersUsing(CreateNewUser::class)、Fortify::resetUserPasswordsUsing(...)、Fortify::updateUserProfileInformationUsing(...)、Fortify::updateUserPasswordsUsing(...); - 注册视图回调:
Fortify::registerView()— 先检查instanceSettings()->is_registration_enabled,关闭则重定向到登录页;否则渲染auth.register,并传入isFirstUser(User::count() === 0)标志;Fortify::loginView()— 若系统中尚无任何用户,直接重定向到注册页;否则渲染auth.login,并传入注册开关与已启用的 OAuth 供应商列表(OauthSetting::where('enabled', true));Fortify::requestPasswordResetLinkView()/Fortify::resetPasswordView()— 分别绑定auth.forgot-password与auth.reset-password;Fortify::confirmPasswordView()/Fortify::twoFactorChallengeView()— 绑定密码确认与 2FA 挑战页。
Fortify::authenticateUsing()自定义认证管线。这是 Coolify 最重量级的定制点(FortifyServiceProvider.php#L73-L104):
Fortify::authenticateUsing(function (Request $request) {
$email = strtolower($request->email);
$user = User::where('email', $email)->with('teams')->first();
if ($user && Hash::check($request->password, $user->password)) {
// ...
}
});
其内部还处理了团队邀请的自动接受:如果该邮箱存在一个未过期且有效的 TeamInvitation,用户首次登录时会被自动附加到被邀请的团队(携带邀请中的 role),currentTeam 设为该团队并删除邀请记录;否则走正常流程——取 personal_team 作为当前团队,缺失时通过 recreate_personal_team() 重建。最后将 currentTeam 写入 session。由于 User 模型 的 setEmailAttribute 会把邮箱统一小写化,这里的 strtolower 与之配合保证大小写不一致时也能登录成功。
功能特性启用的完整工作流
以下工作流清单继承自技能文档,并结合 Coolify 仓库中的实际文件标注了每一步的落点。
两步验证(2FA)
- [ ] 给
User模型添加TwoFactorAuthenticatable特性 —— Coolify 中位于 app/Models/User.php 的use列表; - [ ] 在
config/fortify.php的features中启用Features::twoFactorAuthentication()(Coolify 附带confirm => true、confirmPassword => true); - [ ] 若缺少
*_add_two_factor_columns_to_users_table.php迁移,执行php artisan vendor:publish --tag=fortify-migrations后再migrate; - [ ] 在
FortifyServiceProvider中设置Fortify::twoFactorChallengeView()(Coolify 已绑定auth.two-factor-challenge); - [ ] 构建 2FA 管理界面(启用/禁用、恢复码展示);
- [ ] 测试二维码扫码与恢复码登录流程。
Coolify 仓库中已内置对应迁移 database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php:无条件添加可空的 two_factor_secret 与 two_factor_recovery_codes 两列;仅当 Fortify::confirmsTwoFactorAuthentication() 为真时才添加 two_factor_confirmed_at 列——这解释了为何 config 中的 confirm 选项会直接影响数据库结构。User 模型也在 $hidden 中隐藏了 two_factor_secret 与 two_factor_recovery_codes,防止敏感字段随 API 序列化泄露。
Passkey(WebAuthn)
- [ ] 给
User模型添加PasskeyAuthenticatable特性并实现PasskeyUser接口; - [ ] 在
config/fortify.php启用Features::passkeys(); - [ ] 若缺少 passkeys 表迁移,
php artisan vendor:publish --tag=fortify-migrations并迁移; - [ ] 按需配置
relying_party_id、allowed_origins、user_handle_secret、timeout等参数(默认值不适合生产时); - [ ] 基于
@laravel/passkeys前端包构建注册、登录、确认、删除四种交互界面。
Coolify 当前 config/fortify.php 的 features 中未启用该特性,即 Passkey 相关端点(/passkeys/login 等)处于未注册状态。
邮箱验证
- [ ] 在 config 中启用
Features::emailVerification(); - [ ]
User模型实现MustVerifyEmail接口; - [ ] 设置
verifyEmailView回调; - [ ] 在受保护路由上挂
verified中间件; - [ ] 测试"注册 → 收信 → 点击验证链接"完整流程。
再次强调 Coolify 的特殊性:它刻意没有启用这一 Fortify 特性(config 中被注释),而是自实现了"自托管即验证、云版本异步验证邮件"的分支逻辑,并额外提供了邮箱变更验证流程(pending_email + 6 位验证码 + 过期时间,见 User::requestEmailChange())。
密码重置
- [ ] 启用
Features::resetPasswords(); - [ ] 设置
requestPasswordResetLinkView回调(Coolify:auth.forgot-password); - [ ] 设置
resetPasswordView回调(Coolify:auth.reset-password); - [ ] 若禁用了视图,需自行定义
password.reset命名路由; - [ ] 测试"申请重置 → 收信 → 链接重置"全流程。
重置邮件由 User::sendPasswordResetNotification() 发出,委托给 TransactionalEmails\ResetPassword 通知类。
SPA 模式('views' => false)
- [ ] 在
config/fortify.php中设置'views' => false; - [ ] 安装并配置 Laravel Sanctum 以支撑基于会话的 SPA 认证;
- [ ] 确保使用
webguard(会话认证的前提); - [ ] 配置好 CSRF 令牌处理;
- [ ] 测试 XHR 认证流程。
当 views 为 false 时,Fortify 一律返回 JSON 而非重定向。典型场景是 2FA 挑战:若已启用两步验证的用户发起登录,登录请求会返回:
{
"two_factor": true
}
前端据此切换到"输入 TOTP 码 / 恢复码"的挑战界面,随后向 /two-factor-challenge 提交。
限流体系:从 limiters 配置到真实实现
技能文档指出:限流通过 fortify.limiters.login 等配置项指定限流器名称,默认按"用户名 + IP"组合节流。Coolify 的完整链路是两段式:
第一段:config/fortify.php 声明名称映射:
'limiters' => [
'login' => 'login',
'two-factor' => 'two-factor',
'forgot-password' => 'forgot-password',
],
第二段:RouteServiceProvider::configureRateLimiting() 给出每个限流器的真实策略:
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by((string) $request->email.'|'.auth_rate_limit_ip($request));
});
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
- login:每个"邮箱 | IP"组合每分钟最多 5 次——即技能文档所说的 username + IP 组合节流,
auth_rate_limit_ip()辅助函数统一取 IP(可适配代理头); - two-factor:按 session 中暂存的
login.id计数,每分钟 5 次,避免 2FA 挑战接口被暴力猜测 TOTP 码; - forgot-password:双重限流——IP 维度 10 分钟 3 次 + 归一化邮箱身份维度每小时 3 次,与注册限流的"邮箱身份"思路一脉相承;
- 同文件还有
magic-link、force-password-reset等 Coolify 自定义限流器,供非 Fortify 路由使用。
此外,CreateNewUser 内的注册限流是独立于上述 limiters 的第三层防护(因为注册失败路径不经过 Fortify 的限流中间件),用 RateLimiter::tooManyAttempts() / RateLimiter::hit() 手动计数,详见上文。
最佳实践速查
技能文档给出的三条最佳实践,在 Coolify 中均有对应实例:
- 自定义认证逻辑:用
Fortify::authenticateUsing()覆盖用户检索方式(Coolify 借此实现了邮箱小写化 + 邀请自动接受),用Fortify::authenticateThrough()定制认证管线;用AppServiceProvider中的契约绑定覆盖响应跳转(Coolify 选择在FortifyServiceProvider::register()中覆盖RegisterResponse); - 注册定制:直接修改
app/Actions/Fortify/CreateNewUser.php来调整验证规则与附加字段——Coolify 在其中加入了注册开关、双维限流、root 用户初始化与邮箱验证分流; - 限流配置:通过
fortify.limiters.login换绑限流器名称,并在RouteServiceProvider中调整阈值与计数键。
小结
Fortify 在 Coolify 中的定位是"路由与认证管线的提供者,而非界面的提供者":config/fortify.php 决定开启哪些能力与端点,app/Actions/Fortify/ 四个类承载可替换的业务规则,FortifyServiceProvider 负责把视图、Action 与响应契约接到 Coolify 自己的团队(Team)模型上。理解这三层扩展点,即可在不改动 Fortify 包源码的前提下,完成登录跳转定制、注册策略收紧、2FA/Passkey 启用与限流策略调整等常见需求。涉及 TOTP 实现细节、恢复码处理、Passkey 配置项等更深入的包内机制,可进一步查阅 Laravel Fortify 官方文档与包源码。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00