skip to content

Fortify Auth Backend

Fortify is the headless auth backend behind Laravel's starter kits: config toggles features, and fortify:install publishes action classes you own. Interviewers ask how much of that code you maintain.

on this pageshow

explore

questions

6

In Laravel, what is Fortify, what does the features array in config/fortify.php control, and what does 'views' => false change?

level: juniorimportance: must knowfreq 45%

answer

  1. headless: routes and controllers, no UI
  2. starter kits use it internally
  3. features array switches route groups on
  4. views false drops the GET screens
  5. not Sanctum: no tokens, no SPA cookies

basics

~20 s

Fortify is Laravel's headless authentication backend: it registers login, registration, reset, verification, two-factor and passkey routes and controllers but ships no UI. The features array decides which route groups exist; 'views' => false removes the GET routes that return screens.

solid answer

~40 s

Fortify (`laravel/fortify`, 1.40 here) is a frontend-agnostic auth backend: install it with `composer require laravel/fortify` and `php artisan fortify:install`, then point your own screens at the routes it registers. The current starter kits use it internally. In `config/fortify.php` the `features` array is the switchboard: each entry, such as `Features::registration()`, `Features::resetPasswords()`, `Features::emailVerification()`, `Features::updateProfileInformation()`, `Features::updatePasswords()`, `Features::twoFactorAuthentication([...])` or `Features::passkeys([...])`, registers that feature's routes; leave one out and its routes do not exist. Login, logout and password confirmation are always registered. `'views' => true` also registers GET routes such as `/login` and `/register` that render whatever you bind with `Fortify::loginView()` and friends; `'views' => false` drops those GET routes for an SPA that draws its own screens, while the POST endpoints stay. Fortify is not Sanctum: Sanctum authenticates requests, Fortify implements the flows.

go deeper

for a junior

Recall that Fortify is a headless auth backend with routes but no UI, and that the features array turns flows such as registration and two-factor on or off.

for a middle

Explain the GET-view versus POST-action split, what views false removes, which routes exist regardless of features, and why Fortify and Sanctum often appear together.

for a senior

Decide which features a product should expose, and know the stub differs from the package defaults, for example email verification commented out.

for a principal

Weigh adopting Fortify's flows against owning hand-written auth, counting upgrade cost, security review scope and how much the team will customise.

## What Fortify is **Laravel Fortify** is a **headless authentication backend**: a package that registers the routes and controllers for Laravel's account flows (login, logout, registration, password reset, email verification, profile and password updates, password confirmation, two-factor authentication and passkeys) without shipping any HTML, CSS or JavaScript. You bring the screens; Fortify brings the endpoints behind them. The current Laravel starter kits (React, Vue, Svelte and Livewire) are built on Fortify, so an application created from a kit already has it installed and configured. Without a kit you install it yourself: 1. `composer require laravel/fortify` 2. `php artisan fortify:install`, which publishes `config/fortify.php`, the action classes in `app/Actions/Fortify`, `app/Providers/FortifyServiceProvider.php` and the migrations, and registers the provider in `bootstrap/providers.php`. 3. `php artisan migrate`. The docs are explicit that Fortify is optional: you can build the same flows with Laravel's auth services by hand. ## The features array `config/fortify.php` has a `features` key holding an array of `Laravel\Fortify\Features` calls. Each call returns a feature name, and Fortify's route file only registers a feature's routes when that name is in the array. The published stub in Fortify 1.40 enables: | Feature | Routes it adds (examples) | |---|---| | `Features::registration()` | `POST /register` | | `Features::resetPasswords()` | `POST /forgot-password`, `POST /reset-password` | | `Features::updateProfileInformation()` | `PUT /user/profile-information` | | `Features::updatePasswords()` | `PUT /user/password` | | `Features::twoFactorAuthentication([...])` | `/two-factor-challenge`, `/user/two-factor-authentication` and more | | `Features::passkeys([...])` | `/passkeys/login`, `/user/passkeys` and more | `Features::emailVerification()` is present but **commented out** in the stub, even though the package's own default config enables it; applications get the stub. Login (`/login`), logout (`/logout`) and the password-confirmation endpoints are registered regardless of the array. Some features take options. The stub passes `['confirm' => true, 'confirmPassword' => true]` to `twoFactorAuthentication` and `['confirmPassword' => true]` to `passkeys`. ## `'views' => false` Fortify splits each screen into a **GET route that returns a view** and a **POST (or PUT/DELETE) route that does the work**. With `'views' => true` (the default) it registers the GET routes too: `/login`, `/register`, `/forgot-password`, `/reset-password/{token}`, `/email/verify`, `/user/confirm-password` and `/two-factor-challenge`. Each returns whatever you bound, for example `Fortify::loginView(fn () => view('auth.login'))`; if you never bind one, the route has nothing to render. Setting `'views' => false` removes those GET routes. It suits a single-page application that renders its own screens and only calls the endpoints. One catch from the docs: the reset-password notification builds its link from a route named `password.reset`, so an SPA that disables views must still define a route with that name. ## Fortify versus Sanctum The two are complementary, not competing: - **Fortify** implements the **flows**: registering, logging in, resetting, enabling two-factor authentication. - **Sanctum** implements **request authentication** for SPAs and API tokens; it has no registration or reset routes. An SPA backend commonly uses both: Fortify for the flows on the `web` guard, Sanctum so the SPA's later API calls are authenticated with the session cookie. ## In a tax-filing application A tax-filing product that wants its own Vue front end but not a starter kit's screens would install Fortify, set `'views' => false`, keep `registration`, `resetPasswords` and `twoFactorAuthentication`, and drop `updateProfileInformation` if profile edits go through a custom, audited endpoint instead. ## Common misreadings in interviews - **"Fortify is a starter kit."** It is the backend the kits sit on; it has no screens of its own. - **"Removing a feature hides it."** Removing a feature removes its routes; there is nothing to hide. - **"`views => false` turns Fortify off."** Only the GET screens go; every POST, PUT and DELETE endpoint stays. - **"The package defaults are what my app runs."** The app runs the published stub, which differs: for example `lowercase_usernames` is `true` in the stub and `false` in the package, and email verification is commented out in the stub. - **"Fortify replaces Laravel's auth services."** It calls them: guards, the password broker and the hasher are all Laravel's.

  • What happens to the login screen if views is true but you never call Fortify::loginView()?
    The `GET /login` route is registered, but its controller resolves the `LoginViewResponse` contract, and only `Fortify::loginView()` binds an implementation. With nothing bound, the request fails. Either bind a view in `FortifyServiceProvider::boot()` or set `'views' => false`.
  • Why must an SPA with views disabled still define a password.reset route?
    Laravel's `ResetPassword` notification builds the link in the email from the named route `password.reset`. With views disabled Fortify no longer registers that GET route, so the application must define one, typically pointing at the SPA's reset screen, or sending the email fails.

saying these in an interview costs you the question

  • Fortify ships ready-made Blade login and registration pages.
  • Fortify and Sanctum are alternatives, so you pick one.
  • Setting views to false disables the POST login endpoint too.
  • Removing a feature from the array only hides its link in the UI.
  • The Fortify stub enables email verification by default.
open as a page

When adding Fortify two-factor authentication to a Laravel tax-filing app, what do the confirm and confirmPassword options change, and how does login become two steps?

level: seniorimportance: must knowfreq 40%

basics

~20 s

With confirm => true, enabling stores a secret but login is not challenged until the user submits a valid code; confirmPassword => true puts password.confirm on the management routes. Login then stops at /two-factor-challenge before signing in.

open as a page

After php artisan fortify:install, which classes in app/Actions/Fortify does a Laravel app own, and how does Fortify call them?

level: middleimportance: should knowfreq 35%

basics

~10 s

fortify:install publishes CreateNewUser, ResetUserPassword, UpdateUserPassword, UpdateUserProfileInformation and a PasswordValidationRules trait into app/Actions/Fortify. They are your code; FortifyServiceProvider registers them with Fortify::createUsersUsing() and friends, and Fortify's controllers call them.

open as a page

How does Laravel Fortify throttle login attempts, and what changes when config/fortify.php sets limiters.login to a named rate limiter?

level: middleimportance: should knowfreq 30%

basics

~20 s

With limiters.login empty, Fortify's own EnsureLoginIsNotThrottled step allows 5 failed attempts per username-and-IP key before a 429. With limiters.login set to a name, as in the stub, the login route gets throttle:login instead, using the RateLimiter::for('login') definition in FortifyServiceProvider.

open as a page

In Laravel Fortify, what does Fortify::authenticateUsing replace in the login pipeline, and what must its callback take care of?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Fortify::authenticateUsing replaces the credential check: instead of guard->attempt(), Fortify calls your closure, which must verify the credentials and return the user or null/false; Fortify then calls login() on the guard. Throttling, two-factor and session regeneration still run around it.

open as a page

How do you enable passkeys with Laravel Fortify 1.40, and what does the application still have to provide itself?

level: middleimportance: nice to knowfreq 15%

basics

~10 s

Add Features::passkeys() to config/fortify.php, make User implement Laravel\Fortify\Contracts\PasskeyUser and use PasskeyAuthenticatable, run the migrations, and set relying_party_id and allowed_origins. The app still provides the screens and the browser WebAuthn calls, usually through @laravel/passkeys.

open as a page