首页
/ Coolify 中的 Laravel Fortify 认证体系:配置项、可覆盖 Action 与关键端点实战解析

Coolify 中的 Laravel Fortify 认证体系:配置项、可覆盖 Action 与关键端点实战解析

2026-09-05 13:35:33作者:庞眉杨Will

本篇以 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.loginauth.register);
  • 业务逻辑(用户创建、密码重置、资料更新)集中在 app/Actions/Fortify/ 目录下的四个类中,通过契约(Contracts)注入。

从源码结构看,Fortify 的扩展点可归纳为五类:路由/端点(包内注册)、Actions(可覆盖的业务逻辑)、Config(特性开关与全局选项)、Contracts(可覆盖的响应类,如 LoginResponseLogoutResponseRegisterResponse)、视图回调(在 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 logintwo-factorforgot-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,
    ]),
],

两点值得注意:

  1. 邮箱验证特性被注释掉了。从源码看,Coolify 没有让 User 实现 MustVerifyEmailapp/Models/User.php 仅实现了 SendsEmail 接口),而是用自建流程替代:自托管部署时用户创建后直接调用 markEmailAsVerified(),云版本则派发 SendVerificationEmailJob(见下文 CreateNewUser 分析)。
  2. 两步验证启用了 confirm => trueconfirmPassword => 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 校验(即对照 web guard 的当前密码),新密码同样要求 Password::defaults() + confirmed,错误信息使用 updatePassword 校验 bag;
  • UpdateUserProfileInformation.php(实现 UpdatesUserProfileInformation):校验 nameemailRule::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 完成实例初始化,其余用户回首页。同理,LoginResponseLogoutResponse 等契约也可按同样方式覆盖以实现自定义跳转。

boot():视图回调与自定义认证

boot() 中依次完成:

  1. 注入 ActionsFortify::createUsersUsing(CreateNewUser::class)Fortify::resetUserPasswordsUsing(...)Fortify::updateUserProfileInformationUsing(...)Fortify::updateUserPasswordsUsing(...)
  2. 注册视图回调
    • Fortify::registerView() — 先检查 instanceSettings()->is_registration_enabled,关闭则重定向到登录页;否则渲染 auth.register,并传入 isFirstUserUser::count() === 0)标志;
    • Fortify::loginView() — 若系统中尚无任何用户,直接重定向到注册页;否则渲染 auth.login,并传入注册开关与已启用的 OAuth 供应商列表(OauthSetting::where('enabled', true));
    • Fortify::requestPasswordResetLinkView() / Fortify::resetPasswordView() — 分别绑定 auth.forgot-passwordauth.reset-password
    • Fortify::confirmPasswordView() / Fortify::twoFactorChallengeView() — 绑定密码确认与 2FA 挑战页。
  3. 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.phpuse 列表;
  • [ ] 在 config/fortify.phpfeatures 中启用 Features::twoFactorAuthentication()(Coolify 附带 confirm => trueconfirmPassword => 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_secrettwo_factor_recovery_codes 两列;仅当 Fortify::confirmsTwoFactorAuthentication() 为真时才添加 two_factor_confirmed_at——这解释了为何 config 中的 confirm 选项会直接影响数据库结构。User 模型也在 $hidden 中隐藏了 two_factor_secrettwo_factor_recovery_codes,防止敏感字段随 API 序列化泄露。

Passkey(WebAuthn)

  • [ ] 给 User 模型添加 PasskeyAuthenticatable 特性并实现 PasskeyUser 接口;
  • [ ] 在 config/fortify.php 启用 Features::passkeys()
  • [ ] 若缺少 passkeys 表迁移,php artisan vendor:publish --tag=fortify-migrations 并迁移;
  • [ ] 按需配置 relying_party_idallowed_originsuser_handle_secrettimeout 等参数(默认值不适合生产时);
  • [ ] 基于 @laravel/passkeys 前端包构建注册、登录、确认、删除四种交互界面。

Coolify 当前 config/fortify.phpfeatures 中未启用该特性,即 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 认证;
  • [ ] 确保使用 web guard(会话认证的前提);
  • [ ] 配置好 CSRF 令牌处理;
  • [ ] 测试 XHR 认证流程。

viewsfalse 时,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-linkforce-password-reset 等 Coolify 自定义限流器,供非 Fortify 路由使用。

此外,CreateNewUser 内的注册限流是独立于上述 limiters 的第三层防护(因为注册失败路径不经过 Fortify 的限流中间件),用 RateLimiter::tooManyAttempts() / RateLimiter::hit() 手动计数,详见上文。

最佳实践速查

技能文档给出的三条最佳实践,在 Coolify 中均有对应实例:

  1. 自定义认证逻辑:用 Fortify::authenticateUsing() 覆盖用户检索方式(Coolify 借此实现了邮箱小写化 + 邀请自动接受),用 Fortify::authenticateThrough() 定制认证管线;用 AppServiceProvider 中的契约绑定覆盖响应跳转(Coolify 选择在 FortifyServiceProvider::register() 中覆盖 RegisterResponse);
  2. 注册定制:直接修改 app/Actions/Fortify/CreateNewUser.php 来调整验证规则与附加字段——Coolify 在其中加入了注册开关、双维限流、root 用户初始化与邮箱验证分流;
  3. 限流配置:通过 fortify.limiters.login 换绑限流器名称,并在 RouteServiceProvider 中调整阈值与计数键。

小结

Fortify 在 Coolify 中的定位是"路由与认证管线的提供者,而非界面的提供者":config/fortify.php 决定开启哪些能力与端点,app/Actions/Fortify/ 四个类承载可替换的业务规则,FortifyServiceProvider 负责把视图、Action 与响应契约接到 Coolify 自己的团队(Team)模型上。理解这三层扩展点,即可在不改动 Fortify 包源码的前提下,完成登录跳转定制、注册策略收紧、2FA/Passkey 启用与限流策略调整等常见需求。涉及 TOTP 实现细节、恢复码处理、Passkey 配置项等更深入的包内机制,可进一步查阅 Laravel Fortify 官方文档与包源码。

登录后查看全文
热门项目推荐
相关项目推荐