After php artisan fortify:install, which classes in app/Actions/Fortify does a Laravel app own, and how does Fortify call them?
answer
- published stubs, now your code
- CreateNewUser, ResetUserPassword and two more
- PasswordValidationRules trait
- wired in FortifyServiceProvider::boot
- each implements a Fortify contract
basics
~10 sfortify: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.
solid answer
~30 sThe command publishes four action classes and one trait into `app/Actions/Fortify`: `CreateNewUser` (validates input and creates the user), `ResetUserPassword`, `UpdateUserPassword`, `UpdateUserProfileInformation`, and `PasswordValidationRules`, which holds the shared password rules. Each class implements a Fortify contract such as `Laravel\Fortify\Contracts\CreatesNewUsers`. The published `App\Providers\FortifyServiceProvider` wires them up in `boot()` with `Fortify::createUsersUsing(CreateNewUser::class)`, `updateUserProfileInformationUsing`, `updateUserPasswordsUsing` and `resetUserPasswordsUsing`; Fortify's controllers resolve the contract and call the method. Once published they are application code: `composer update` never touches them, so adding a field, say a taxpayer reference at registration, means editing `CreateNewUser`, and upstream fixes to the stubs reach you only if you port them by hand.
code
php · 31 lines<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\CreatesNewUsers;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
public function create(array $input): User
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', Rule::unique(User::class)],
'taxpayer_ref' => ['required', 'string', 'size:10', Rule::unique(User::class)],
'password' => $this->passwordRules(),
])->validate();
return User::create([
'name' => $input['name'],
'email' => $input['email'],
'taxpayer_ref' => $input['taxpayer_ref'],
'password' => Hash::make($input['password']),
]);
}
}go deeper
Know the four published action classes and that you edit CreateNewUser to change what registration validates and stores.
Explain the contract-plus-container binding set up in FortifyServiceProvider, and why the actions are testable without HTTP.
Treat published stubs as owned code: track upstream changes, keep rules in PasswordValidationRules, and disable features in config rather than deleting classes.
Set a policy for how far the team may diverge from Fortify's stubs before the maintenance cost argues for owning the whole auth layer.
## What `fortify:install` publishes `php artisan fortify:install` runs `vendor:publish` for Fortify's service provider and registers `App\Providers\FortifyServiceProvider` in `bootstrap/providers.php`. The published files are: - `config/fortify.php` - `app/Providers/FortifyServiceProvider.php` - `app/Actions/Fortify/CreateNewUser.php` - `app/Actions/Fortify/ResetUserPassword.php` - `app/Actions/Fortify/UpdateUserPassword.php` - `app/Actions/Fortify/UpdateUserProfileInformation.php` - `app/Actions/Fortify/PasswordValidationRules.php` - migrations: Fortify's two-factor columns and the `laravel/passkeys` migration Everything in `app/` is now **your code**. That is the whole design: Fortify owns the HTTP layer (routes, controllers, request classes, responses), and you own the business rules of each flow. ## The action classes and their contracts | Class | Contract it implements | Called when | |---|---|---| | `CreateNewUser` | `CreatesNewUsers` | `POST /register` | | `ResetUserPassword` | `ResetsUserPasswords` | `POST /reset-password` after the token checks out | | `UpdateUserPassword` | `UpdatesUserPasswords` | `PUT /user/password` | | `UpdateUserProfileInformation` | `UpdatesUserProfileInformation` | `PUT /user/profile-information` | `PasswordValidationRules` is a trait the classes share; in the Fortify 1.40 stub its `passwordRules()` returns the `Password::default()` rule plus `required`, `string` and `confirmed`, so one edit changes the password policy everywhere. The stub `CreateNewUser::create(array $input)` runs `Validator::make(...)->validate()` for `name`, a unique `email` and the password rules, then calls `User::create()` with `Hash::make($input['password'])`. ## How Fortify finds them The published provider's `boot()` method registers each class: 1. `Fortify::createUsersUsing(CreateNewUser::class)` 2. `Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class)` 3. `Fortify::updateUserPasswordsUsing(UpdateUserPassword::class)` 4. `Fortify::resetUserPasswordsUsing(ResetUserPassword::class)` Each call binds the contract to your class in the container. Fortify's controller type-hints the contract, the container hands it your class, and the controller calls `create()`, `reset()` or `update()` and then returns Fortify's response object. Because the binding goes through the container, your action can type-hint its own dependencies in a constructor. ## What owning them means - **You change behaviour by editing them.** A tax-filing app that needs a taxpayer reference and a country of residence at sign-up adds those rules and columns in `CreateNewUser`, not in a Fortify config key. - **They are not updated by Composer.** A security fix or a new default in Fortify's stubs does not reach an existing app; read Fortify's upgrade notes and port changes yourself. - **They are ordinary classes you can test.** Call `app(CreatesNewUsers::class)->create([...])` in a test without going through HTTP. - **Deleting one breaks its route**, because the controller still resolves the contract. Remove the feature from `features` instead. ## What you do not own The controllers, the login pipeline, the two-factor challenge and the route definitions stay inside the package. To change those you use Fortify's hooks rather than editing vendor code: `Fortify::authenticateUsing()` for credential checks, `Fortify::authenticateThrough()` for the login pipeline, the `*View()` methods for screens, and container bindings for response contracts such as `LoginResponse`. ## Starter kits The current starter kits ship the same kind of `app/Actions/Fortify` classes already published and adapted to the kit, so the ownership rule applies there too. ## A worked change: the tax-filing sign-up Suppose sign-up must capture a ten-character taxpayer reference and reject duplicates: 1. Add a migration for a unique `taxpayer_ref` column on `users`. 2. Add `taxpayer_ref` to the model's fillable attributes, otherwise `User::create()` silently drops it under the default mass-assignment settings. 3. Add the validation rule and the attribute in `CreateNewUser::create()`. 4. Add the field to the registration screen that posts to `/register`. 5. Test the action directly by resolving `CreatesNewUsers` from the container, and test the route once end to end. Nothing in `config/fortify.php` changes, which is the point: flow switches live in config, business rules live in your actions.
- How would you stop users registering in a Fortify app without deleting CreateNewUser?Remove `Features::registration()` from the `features` array in `config/fortify.php`. The `/register` routes are then never registered, and the action class can stay or go without breaking anything.
- Why does a Fortify upgrade not change your registration validation?The rules live in `app/Actions/Fortify/CreateNewUser.php` and `PasswordValidationRules.php`, published once by `fortify:install`. Composer updates `vendor/laravel/fortify`, not `app/`, so any change to the upstream stubs must be copied across by hand.
saying these in an interview costs you the question
- The files in app/Actions/Fortify are updated whenever Fortify is upgraded.
- You customise registration by editing Fortify's controllers in vendor.
- Fortify finds the actions automatically by folder name, without registration.
- Deleting CreateNewUser is the way to turn registration off.
- Password rules must be repeated separately in every action class.