Laravel Fortify: what it is, when to use it, and first steps to create a flexible authentication system
Content Index
- Laravel Fortify: what it is, when to use it, and how to create a flexible authentication system in Laravel
- What is Laravel Fortify and why is it "headless"?
- What it really means that Fortify has no frontend
- When does it make sense to use Laravel Fortify (and when not)?
- Situations where Fortify does not provide advantages
- Laravel Fortify vs Breeze vs Jetstream
- Level of intrusion and maintenance
- Laravel Fortify and Laravel Sanctum: how they complement each other
- Laravel Fortify initial installation and configuration
- Feature configuration in Laravel Fortify
- Using Laravel Fortify without views: total frontend control
- Using Laravel Fortify in APIs and SPA applications
- If Fortify is used only as a backend:
- Redirects that should be modified
- Fortify middlewares you should understand (and adjust)
- RedirectIfAuthenticated
- Authenticate middleware
- Common problems when using Laravel Fortify (and how to solve them)
- 401 error even when authenticated
- CORS errors
- Advanced customization of Laravel Fortify
- Example of use and operation
- Login
- Register
- Conclusion: why Laravel Fortify is ideal for custom authentication
Laravel Fortify is a package for authentication, registration, password recovery, email verification, and more. In short, it allows for the same functionalities as Laravel Breeze, which we used before, but the difference is that it is not as intrusive. When we install Laravel Breeze, it installs Tailwind.css and generates several associated components, controllers, views, and routes.
In the case of Laravel Fortify, this is not the case, and it provides the same features without the need for a graphical interface. Therefore, it is particularly useful if you want to develop a more customized authentication backend than what Breeze offers. It is important to note that if you are using Laravel Breeze or a similar solution, it is not necessary to use Laravel Fortify.
Another possible comparison you might be making is with Sanctum. Laravel Fortify and Laravel Sanctum are not mutually exclusive or competing packages; rather, they can be used in the same project if required. Laravel Sanctum only deals with managing API tokens and authenticating existing users using cookies or session tokens.
Sanctum does not provide any routes that handle user registration, password resets, etc. In short, Sanctum focuses on authenticating a Rest API, and Laravel Fortify is for a traditional web application.
Laravel Fortify: what it is, when to use it, and how to create a flexible authentication system in Laravel
When you're building an application with Laravel and it's time to implement authentication, the same names usually come up: Breeze, Jetstream, Sanctum... and, if you need something more flexible, Laravel Fortify.
In my case, Fortify started to make sense when I needed total control over the frontend, without the framework imposing views, styles, or components that I would end up dismantling anyway. If you've ever installed Breeze "just to try" and then spent more time deleting code than using it, you probably understand why Fortify exists.
In this article, I explain what Laravel Fortify is, when to use it (and when not to), how to configure it correctly, and what real problems usually appear when you take it to production, especially in APIs and SPAs.
What is Laravel Fortify and why is it "headless"?
Laravel Fortify is an authentication backend without a graphical interface.
When it is said to be headless, it means exactly this: Fortify handles all the authentication logic, but does not include views, styles, or UI components.
With Fortify you have, among other things:
- Login and logout
- User registration
- Password recovery
- Email verification
- Password confirmation
- Rate limiting
- Two-factor authentication (2FA)
All of that already implemented, tested, and maintained by the Laravel team... but without deciding for you how your login form should look.
What it really means that Fortify has no frontend
This is key:
Fortify does define routes and controllers, but it does not define views. If you try to access /login without having configured anything, you will get an error like:
Target [Laravel\Fortify\Contracts\LoginViewResponse] is not instantiable.
It's not a bug. It's Fortify telling you: "you tell me what you want your UI to look like."
This is exactly what makes it so powerful when working with:
- Custom Blade
- Vue / React
- SPAs
- Pure APIs
When does it make sense to use Laravel Fortify (and when not)?
This is where many people go wrong.
Cases where Fortify fits better than Breeze
- Laravel Fortify makes perfect sense when:
- You want total frontend control
- You don't want Tailwind, components, or automatic scaffolding
- You are building an SPA or an API
- You need to customize authentication flows
- You care about medium/long-term maintainability
In real projects, Fortify has been especially useful when the backend and frontend evolve independently.
Situations where Fortify does not provide advantages
Fortify is not the best option if:
- You want something quick and visual from minute one
- You are prototyping
- You don't care about the default frontend
- Your app is small and won't grow
In these cases, Laravel Breeze is more than enough and probably more productive.
Laravel Fortify vs Breeze vs Jetstream
Differences in architecture and frontend control
- Breeze
- Authentication + views + Tailwind + ready-to-use controllers.
Very comfortable, but intrusive.
- Authentication + views + Tailwind + ready-to-use controllers.
- Jetstream
- Even more complete (teams, profiles, etc.).
Ideal for large apps, but opinionated, I no longer recommend using Jetstream as it is no longer the official one.
- Even more complete (teams, profiles, etc.).
- Fortify
- Backend only. You decide everything else.
I usually summarize it like this:
Breeze and Jetstream give you a furnished house; Fortify gives you the foundations.
Level of intrusion and maintenance
One of the problems with Breeze is that, as the project grows, you end up fighting with the scaffolding. With Fortify, that doesn't happen: you only consume endpoints and logic.
Laravel Fortify and Laravel Sanctum: how they complement each other
This is a point where there is a lot of confusion.
Laravel Fortify and Laravel Sanctum do not compete.
- Fortify:
- Web authentication (login, registration, reset, 2FA...)
- Sanctum:
- API authentication (cookies, tokens)
In many real projects I have used Fortify + Sanctum together:
- Fortify to manage users
- Sanctum to authenticate API requests
If you are building an SPA, this combination is practically standard.
Laravel Fortify initial installation and configuration
To install Laravel Fortify we use the following command:
$ composer require laravel/fortifyThe next step is to execute the installation command:
$ php artisan fortify:installThis command will generate the migrations, configuration file, provider, among others.
We execute the migrations:
$ php artisan migrateThis generates:
- Migrations
- config/fortify.php
- FortifyServiceProvider
- Actions for login, registration, etc.
From here, Fortify already works... although it still doesn't "look" like it.
And with this, we can now use Laravel Fortify.
Feature configuration in Laravel Fortify
One of the things I like most about Fortify is that you can activate only what you need through features that we can enable or disable as desired:
config\fortify.php
'features' => [
Features::registration(),
Features::resetPasswords(),
Features::emailVerification(),
],If you don't want email verification, simply comment it out.
This modular approach makes Fortify adapt very well to real projects, where you don't always need everything from the beginning.
These allow enabling/disabling the registration option, resetting the password, and email verification respectively. If you do not want to use any or several of these options, you simply have to comment them out, for example, if you want to deactivate email verification:
'features' => [
Features::registration(),
Features::resetPasswords(),
// Features::emailVerification(),
],Using Laravel Fortify without views: total frontend control
If you visit /login or /register without configuring anything, Fortify intentionally fails. That forces you to decide:
- Blade?
- Vue?
- React?
- External SPA?
When I need simple Blade, I usually define the views in the FortifyServiceProvider:
Fortify::loginView(function () {
return view('auth.login');
});The same for registration. Fortify imposes nothing, it just expects you to do it.
Using Laravel Fortify in APIs and SPA applications
Disable views in a pure backend
If Fortify is used only as a backend:
'views' => false,This avoids behaviors designed for traditional web applications.
Redirects that should be modified
One of the most common problems I encountered is the automatic redirection to /home when a user is already authenticated.
For an API, this makes no sense.
The solution is to adapt middlewares like RedirectIfAuthenticated to return JSON when the request is AJAX.
Fortify middlewares you should understand (and adjust)
RedirectIfAuthenticated
By default, it redirects. In APIs, this often breaks flows.
Detecting $request->expectsJson() and returning a clear response avoids many headaches.
Authenticate middleware
Another typical adjustment is to redirect to the correct frontend when the user is not authenticated, something fundamental when backend and frontend live on different domains.
Common problems when using Laravel Fortify (and how to solve them)
401 error even when authenticated
Very common when working with Sanctum. Normally due to:
- SANCTUM_STATEFUL_DOMAINS misconfigured
- SESSION_DOMAIN incorrect or unnecessary
In more than one project, the problem was simply having included http:// where it shouldn't have been.
CORS errors
Another classic.
Always check:
- config/cors.php
- allowed_origins
- routes included in paths
Advanced customization of Laravel Fortify
Fortify allows going much further:
- Custom rate limiting
- Change username to email or phone
- Own authentication flows
- 2FA with TOTP or even SMS
I have had cases where the default 2FA did not fit the UX, and Fortify allows overriding practically everything without hacks.
Example of use and operation
To exemplify its use, if we do a:
$ php artisan r:lWe will see all the routes generated by Fortify:
GET|HEAD register ............................................ register › Laravel\Fortify \RegisteredUserController@create
POST register ........................................................ Laravel\Fortify › RegisteredUserController@store
POST reset-password ................................... password.update › Laravel\Fortify › NewPasswordController@store
GET|HEAD reset-password/{token} ........................... password.reset › Laravel\Fortify › NewPasswordController@create
GET|HEAD two-factor-challenge ......... two-factor.login › Laravel\Fortify › TwoFactorAuthenticatedSessionController@create
POST two-factor-challenge ............................. Laravel\Fortify › TwoFactorAuthenticatedSessionController@store
GET|HEAD up ...............................................................................................................
GET|HEAD user/confirm-password ....................................... Laravel\Fortify › ConfirmablePasswordController@show
POST user/confirm-password ................... password.confirm › Laravel\Fortify › ConfirmablePasswordController@store
GET|HEAD user/confirmed-password-status .. password.confirmation › Laravel\Fortify › ConfirmedPasswordStatusController@show
POST user/confirmed-two-factor-authentication two-factor.confirm › Laravel\Fortify › ConfirmedTwoFactorAuthenticationC…
PUT user/password ................................. user-password.update › Laravel\Fortify › PasswordController@update
PUT user/profile-information . user-profile-information.update › Laravel\Fortify › ProfileInformationController@update
POST user/two-factor-authentication ..... two-factor.enable › Laravel\Fortify › TwoFactorAuthenticationController@store
DELETE user/two-factor-authentication .. two-factor.disable › Laravel\Fortify › TwoFactorAuthenticationController@destroy
GET|HEAD user/two-factor-qr-code .................... two-factor.qr-code › Laravel\Fortify › TwoFactorQrCodeController@show
GET|HEAD user/two-factor-recovery-codes ........ two-factor.recovery-codes › Laravel\Fortify › RecoveryCodeController@index
POST user/two-factor-recovery-codes .................................... Laravel\Fortify › RecoveryCodeController@store
GET|HEAD user/two-factor-secret-key ........... two-factor.secret-key › Laravel\Fortify › TwoFactorSecretKeyController@showIf we enter the login route, we will see an error like the following:
http://larafirstepspackages.test/login
Target [Laravel\Fortify\Contracts\LoginViewResponse] is not instantiable.Since, as we mentioned before, we do not have ready-made pages or screens like in Breeze, we must create them manually (or use them with other technologies such as consuming these routes through a Vue app).
If you comment out the register module:
config\fortify.php
// Features::registration(),And try to go to the route:
http://larafirstepspackages.test/register
You will see that it returns a 404 page because we have just disabled the action to register users.
If you explore the object:
FortifyWe will see many options that we can customize in Fortify; if we review the file:
app\Providers\FortifyServiceProvider.php
We will see all the Fortify actions that we can customize, such as the lockout time after failed logins:
RateLimiter::for('login', function (Request $request) {
***Login
For example, we can specify the view for login:
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
public function boot(): void
{
***
Fortify::loginView(function(){
return view('auth.login');
});
}
}And its view:
resources\views\auth\login.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
@if ($errors->any())
@foreach ($errors->all() as $e)
<div>
{{ $e }}
</div>
@endforeach
@endif
<form action="" method="post">
@csrf
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="Send">
</form>
</body>
</html>Register
Or to register:
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
public function boot(): void
{
***
Fortify::registerView(function(){
return view('auth.register');
});
}
}Let's create a very simple view like the following:
resources\views\auth\register.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
@if ($errors->any())
@foreach ($errors->all() as $e)
<div>
{{ $e }}
</div>
@endforeach
@endif
<form action="" method="post">
@csrf
<input type="text" name="name" placeholder="name">
<input type="email" name="email">
<input type="password" name="password" >
<input type="submit" value="Send">
</form>
</body>
</html>By entering the corresponding routes:
http://larafirstepspackages.test/login
http://larafirstepspackages.test/register
You will see the complete system for login and registration provided by Fortify; these are just some of the actions available, explore their use through the previous examples to practically understand how the package works.
With this, it is up to the reader to explore the rest of the functionalities so that you can use them in your projects that require using Fortify to create a completely customized authentication system instead of Breeze:
https://laravel.com/docs/master/fortify
Frequently asked questions about Laravel Fortify
- Does Laravel Fortify include frontend?
- No. It is completely headless.
- Can Fortify be used with Vue or React?
- Yes, and it is one of its best use cases.
- Does Fortify replace Sanctum?
- No. They complement each other.
- Is it a good idea to use Fortify in small projects?
- Only if you know that the project will grow or needs customization.
Conclusion: why Laravel Fortify is ideal for custom authentication
Laravel Fortify is not for all projects, but when it fits, it fits very well.
If you need:
- Flexibility
- Control
- Clean backend
- Integration with SPAs or APIs
Fortify is one of the best decisions you can make in Laravel.
It doesn't give you a pretty UI, but it gives you something more important: freedom.
I agree to receive announcements of interest about this Blog.
Laravel Fortify is a package for authentication, registration, password recovery, email verification and more, in short, it allows you to perform the same functionalities as Laravel Breeze that we used before, but without the graphical part.