Coolify 中的 Laravel Fortify 认证体系:配置项、可覆盖 Action 与关键端点实战解析
【免费下载链接】coolifyAn open-source, self-hostable PaaS alternative to Vercel, Heroku & Netlify that lets you easily deploy static sites, databases, full-stack applications and 280+ one-click services on your own servers.项目地址: https://gitcode.com/GitHub_Trending/co/coolify
本篇以 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 官方文档与包源码。
【免费下载链接】coolifyAn open-source, self-hostable PaaS alternative to Vercel, Heroku & Netlify that lets you easily deploy static sites, databases, full-stack applications and 280+ one-click services on your own servers.项目地址: https://gitcode.com/GitHub_Trending/co/coolify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考