zacksmash/fortify-ui
A ready-made UI layer for Laravel Fortify, adding Blade views and components for login, registration, password reset, email verification, and two-factor authentication so you can scaffold secure auth screens without building them from scratch.
Fortify UI is a Blade/Tailwind view layer for Laravel Fortify — Fortify gives you the headless authentication backend (routes, actions, 2FA endpoints), and this package supplies the screens so you don't hand-roll login, registration, password reset, email verification, and two-factor setup.
Minimal path:
composer require zacksmash/fortify-ui (Laravel Fortify should already be installed and its FortifyServiceProvider registered — this package builds on it, it doesn't replace it).php artisan fortify-ui:install. This publishes the auth views (typically into resources/views/auth), plus supporting config/routes depending on your chosen options.config/fortify.php — registration, resetPasswords, emailVerification, updateProfileInformation, and twoFactorAuthentication. If you enable 2FA, add the two_factor_secret, two_factor_recovery_codes, and two_factor_confirmed_at columns to users and migrate.npm install && npm run dev so Tailwind picks up the published views.First use case: confirm the login/register screens render at /login and /register, then walk the 2FA enrollment flow at /user/two-factor-authentication — enable, scan the QR, and submit a code from your authenticator. On v2.0.2 that flow now includes the confirmation step (see Gotchas), which is the single most useful smoke test of this release.
Treat Fortify as the backend, this package as the frontend. Every form posts to Fortify's named routes; the views are just markup. When you need to change behaviour (extra validation, custom user creation), edit the Fortify action classes in app/Actions/Fortify/ rather than the Blade files.
Publish-then-own the views. The normal workflow is to publish once and customise resources/views/auth/* to match your design system. Keep a note of the package version you published from — this is the main source of drift (see Gotchas).
The 2FA flow is a two-phase state machine. Enabling 2FA generates a secret and QR code but the account is not "confirmed" until the user submits a valid TOTP code back to the confirmation endpoint. In Blade terms:
POST /user/two-factor-authentication — enable, generates secretGET /user/two-factor-qr-code — JSON payload for the QR (fetch with credentials so the session cookie is sent)POST /user/confirmed-two-factor-authentication — the confirmation step fixed in v2.0.2GET /user/two-factor-recovery-codes — recovery codes, regenerable via POSTGate anything sensitive behind the password.confirm middleware, and use verified on routes that require a confirmed email.
Configure confirmation explicitly. In config/fortify.php, the 2FA feature array needs 'confirm' => true for the confirmation step to be enforced, and 'confirmPassword' => true if you want password re-entry before enabling/disabling. Without confirm, the newly-fixed confirmation view is effectively unreachable and 2FA presents as active the moment it's enabled.
Testing. Feature-test the enrollment path end to end: enable, assert the secret is set, POST a valid TOTP to the confirmation endpoint, assert two_factor_confirmed_at is populated — then assert a protected route still challenges before confirmation and passes after. Fortify ships helpers for generating valid codes in tests; use those rather than freezing time and hardcoding digits.
Don't mix scaffolds. If Breeze or Jetstream is also installed, you'll get duplicate route names and competing auth views. Pick one authentication UI per application.
Published views don't receive package updates — this is the critical one for v2.0.2. The fix for the missing 2FA confirmation step lives in the package's shipped Blade files. If you published views on v2.0.1 or earlier, upgrading the composer package changes nothing on screen: your app still owns stale copies of the 2FA templates. After upgrading, diff the package's views against your published ones (vendor/zacksmash/fortify-ui/resources/views) and port the confirmation-step markup across manually. Any team that published and edited these files is silently still vulnerable to the bug this release fixes.
Symptom of the bug this release fixes: users can enable 2FA, the UI redirects or re-renders as though setup succeeded, but two_factor_confirmed_at stays null — so twoFactorAuthenticationConfirmed() is false and the account is in a half-enrolled state that can lock users out of recovery codes. If you see reports of "2FA is on but I never got recovery codes," check your published views before anything else.
Missing columns fail quietly-ish. If two_factor_confirmed_at doesn't exist, confirmation writes go nowhere and Fortify's confirmation check never passes. Verify the migration ran rather than assuming a silent success.
Stale caches after publishing. Run php artisan config:clear and php artisan view:clear after install/upgrade; a cached fortify config will keep the old feature flags and make you think the confirmation option "doesn't work."
Tailwind purging. If you don't publish views and render them from the vendor path, your content globs must include ./vendor/zacksmash/fortify-ui/resources/views/**/*.blade.php or the auth screens ship unstyled in production while looking fine in dev.
QR code fetching is session-dependent. The QR endpoint is an authenticated JSON route; a plain fetch without credentials: 'include' or a missing CSRF header returns 401/419 and the modal appears blank. Inspect the network tab before blaming the package.
Extension points worth knowing: Fortify's view-binding methods (Fortify::loginView(), registerView(), etc.) and per-feature config in config/fortify.php let you point at your own templates without forking. Rate limiting lives in FortifyServiceProvider's configureRateLimiting(); redirects after login live in LoginResponse/TwoFactorLoginResponse. None of these require touching published views.
Debugging shortcut: php artisan route:list --path=user/two-factor quickly shows whether the confirmation route is registered and which middleware guards it — the fastest way to distinguish "Fortify misconfigured" from "my template is stale."
How can I help you explore Laravel packages today?