Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

rhand's avatar
Level 6

Replace Laravel UI Authentication with Fortify

We want to use Laravel Fortify instead of Laravel UI default authentication because we want to use mandatory two factor authentication .

So I did steps following a Laracast tutorial.. I first did:

composer require laravel/fortify

and then I published the vendor files:

php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"

I copied over the Fortify routes to fortify.php, copied over the configure routes method to the service provider app/Providers/FortifyServiceProvider.php:

protected function configureRoutes()
    {
        Route::group([
            'namespace' => 'Laravel\Fortify\Http\Controllers',
            'domain' => config('fortify.domain', null),
            'prefix' => config('fortify.prefix'),
        ], function () {
            $this->loadRoutesFrom(base_path('routes/fortify.php'));
        });
    }

The issue I now of course have is that I already have a login, logout, register route. I need to update these to work with basis login with OTP , perhaps keep logout as is but I might need to update to pass on logic to Fortify to have the 2FA session removed when so.

So I wonder how can I update these routes in web.php:

Route::get('login', [Auth\LoginController::class, 'showLoginForm'])->name('login');
Route::post('login', [Auth\LoginController::class, 'login']);
Route::get('logout', [Auth\LoginController::class, 'logout']);
Route::post('logout', [Auth\LoginController::class, 'logout'])->name('logout');
Route::post('password/email', [Auth\ForgotPasswordController::class, 'sendResetLinkEmail'])->name('password.email');
Route::get('password/reset', [Auth\ForgotPasswordController::class, 'showLinkRequestForm'])->name('password.request');
Route::post('password/reset', [Auth\ResetPasswordController::class, 'reset']);
Route::get('password/reset/{token}', [Auth\ResetPasswordController::class, 'showResetForm'])->name('password.reset');
Route::get('register', [Auth\RegisterController::class, 'showRegistrationForm'])->name('register');
Route::post('register', [Auth\RegisterController::class, 'register'])->middleware('honeypot');

and avoid errors like:

php artisan route:cache                                                       

LogicException 

Unable to prepare route [login] for serialization. Another route has already been assigned name [login].

at vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php:247
243▕             $route->name($this->generateRouteName());

Perhaps I should not replace them but "just" update the controllers and update the profile page and add 2FA activation? What is the way to go here?

0 likes
9 replies
LaryAI's avatar
Level 58

To replace Laravel UI authentication with Laravel Fortify and update the existing routes, you can follow these steps:

  1. Install Laravel Fortify by running the following command in your terminal:
composer require laravel/fortify
  1. Publish the vendor files by running the following command:
php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
  1. Update the configureRoutes method in the app/Providers/FortifyServiceProvider.php file to load the Fortify routes:
protected function configureRoutes()
{
    Route::group([
        'namespace' => 'Laravel\Fortify\Http\Controllers',
        'domain' => config('fortify.domain', null),
        'prefix' => config('fortify.prefix'),
    ], function () {
        $this->loadRoutesFrom(base_path('routes/fortify.php'));
    });
}
  1. Now, you need to update the existing routes in web.php to work with Laravel Fortify. Here's how you can update them:
use Laravel\Fortify\Http\Controllers\AuthenticatedSessionController;
use Laravel\Fortify\Http\Controllers\RegisteredUserController;
use Laravel\Fortify\Http\Controllers\PasswordResetLinkController;
use Laravel\Fortify\Http\Controllers\NewPasswordController;
use Laravel\Fortify\Http\Controllers\ConfirmablePasswordController;
use Laravel\Fortify\Http\Controllers\TwoFactorAuthenticatedSessionController;
use Laravel\Fortify\Http\Controllers\TwoFactorAuthenticationController;
use Laravel\Fortify\Http\Controllers\ProfileInformationController;
use Laravel\Fortify\Http\Controllers\ProfilePhotoController;
use Laravel\Fortify\Http\Controllers\PasswordController;
use Laravel\Fortify\Http\Controllers\EmailVerificationNotificationController;
use Laravel\Fortify\Http\Controllers\EmailVerificationPromptController;
use Laravel\Fortify\Http\Controllers\VerifyEmailController;

Route::get('login', [AuthenticatedSessionController::class, 'create'])->name('login');
Route::post('login', [AuthenticatedSessionController::class, 'store']);
Route::get('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
Route::post('password/email', [PasswordResetLinkController::class, 'store'])->name('password.email');
Route::get('password/reset', [NewPasswordController::class, 'create'])->name('password.request');
Route::post('password/reset', [NewPasswordController::class, 'store'])->name('password.update');
Route::get('password/reset/{token}', [NewPasswordController::class, 'edit'])->name('password.reset');
Route::get('register', [RegisteredUserController::class, 'create'])->name('register');
Route::post('register', [RegisteredUserController::class, 'store'])->middleware('honeypot');

// Two-factor authentication routes
Route::get('two-factor-challenge', [TwoFactorAuthenticatedSessionController::class, 'create'])->name('two-factor.login');
Route::post('two-factor-challenge', [TwoFactorAuthenticatedSessionController::class, 'store']);
Route::post('user/two-factor-authentication', [TwoFactorAuthenticationController::class, 'store'])->name('two-factor.enable');
Route::delete('user/two-factor-authentication', [TwoFactorAuthenticationController::class, 'destroy'])->name('two-factor.disable');

// Profile routes
Route::put('user/profile-information', [ProfileInformationController::class, 'update'])->name('user-profile-information.update');
Route::put('user/photo', [ProfilePhotoController::class, 'update'])->name('user-profile-photo.update');
Route::put('user/password', [PasswordController::class, 'update'])->name('user-password.update');

// Email verification routes
Route::get('email/verify', [EmailVerificationPromptController::class, '__invoke'])->name('verification.notice');
Route::get('email/verify/{id}/{hash}', [VerifyEmailController::class, '__invoke'])->middleware(['signed', 'throttle:6,1'])->name('verification.verify');
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])->middleware(['throttle:6,1'])->name('verification.send');
  1. Make sure to update the corresponding controller namespaces in the routes.

With these updated routes, you should be able to use Laravel Fortify for authentication, including two-factor authentication.

1 like
rhand's avatar
Level 6

I commented out all my older authentication routes in web.php. This so I can load the fortify.php routes. This caused the view not found error as mentioned. Then I added / registered the Fortify Service Provider class

App\Providers\FortifyServiceProvider::class,

to config/app.php and did php artisan optimize . Now /login , /dashboard seem to not load at all. I get the homepage frontend. It is like the Fortify routes are not used which is odd.

Do see the login routes

GET|HEAD        login ........................................................................................................... login › Laravel\Fortify › AuthenticatedSessionController@create
  POST            login ...................................................................................... generated::j0BX2T4qI4wyl4E3 › Laravel\Fortify › AuthenticatedSessionController@store
  POST            logout ........................................................................................................ logout › Laravel\Fortify › AuthenticatedSessionController@destroy

so why does login not load the route added in the boot method:

...
class FortifyServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        Fortify::ignoreRoutes();
    }

...
 $this->configureRoutes();

        Fortify::loginView(function () {
            return view('auth.login');
        });
}

    protected function configureRoutes()
    {
        Route::group([
            'namespace' => 'Laravel\Fortify\Http\Controllers',
            'domain' => config('fortify.domain', null),
            'prefix' => config('fortify.prefix'),
        ], function () {
            $this->loadRoutesFrom(base_path('routes/fortify.php'));
        });
    }
}
...

of app/Providers/FortifyServiceProvider.php ?

rhand's avatar
Level 6

When I do not ignore routes, nor load them from routes/fortify.php and use

<?php

namespace App\Providers;

use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // Fortify::ignoreRoutes();
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Fortify::createUsersUsing(CreateNewUser::class);
        Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
        Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
        Fortify::resetUserPasswordsUsing(ResetUserPassword::class);

        RateLimiter::for('login', function (Request $request) {
            $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());

            return Limit::perMinute(5)->by($throttleKey);
        });

        RateLimiter::for('two-factor', function (Request $request) {
            return Limit::perMinute(5)->by($request->session()->get('login.id'));
        });
        // $this->configureRoutes();

        Fortify::loginView(function () {
            return view('auth.login');
        });
    }

    // protected function configureRoutes()
    // {
    //     Route::group([
    //         'namespace' => 'Laravel\Fortify\Http\Controllers',
    //         'domain' => config('fortify.domain', null),
    //         'prefix' => config('fortify.prefix'),
    //     ], function () {
    //         $this->loadRoutesFrom(base_path('routes/fortify.php'));
    //     });
    // }
}

in app/Providers/FortifyServiceProvider.php I do get the standard login. But this way I lose the option to customize in routes/fortify including all the options in app/fortify.php. And I also see the logging out does not work. It send you to home but does not really log you out. Perhaps I need to see the route for that one too. Perhaps I then better update routes in web.php and turn off Fortify routes..

rhand's avatar
Level 6

For dashboard logout we use

<a "{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();" class="btn btn-primary" ><i class="fa fa-sign-out" aria-hidden="true"></i> &nbsp; {{ __('sign out') }}</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST">
    @csrf
</form>

as suggested at https://laracasts.com/discuss/channels/laravel/laravel-fortify-and-logout by @MichalOravec and also explained at https://www.youtube.com/watch?v=3wKTGO8-R7c&t=13s .

For register and login views we added

Fortify::loginView(function () {
    return view('auth.login');
});
Fortify::registerView(function () {
    return view('auth.register');
});

martinbean's avatar
Level 80

@rhand Don’t run php artisan optimize locally 😫

Run php artisan optimize:clear and never run it again.

As for the topic at hand, you just need to remove the controllers and routes that the laravel/ui package added to your application. You don’t need to delete views; you can just tweak the form URLs, as I think the password route names might be slightly different. Depending on where your views are, you may need to tell Fortify where they are using one of its many methods such as Fortify::viewPrefix('auth.')

1 like
rhand's avatar
Level 6

@martinbean Will not run php artisan optimize locally anymore. Did not realize that is not handy. And yes, also cleared.

We have updated controllers and have moved on some. Enabled 2FA in backend and enabling worked. Just now on login I seem to miss something as I am redirected back to login instead of getting option to add OTP via authenticator.

Summary
URL: https://smtv.test/login
URL: https://smtv.test/two-factor
URL: https://smtv.test/login
Status: 200
Source: Network
Address: 127.0.0.1:443

php artisan route:list shows these routes for logging in and out

POST            login .................................................................................... Laravel\Fortify › AuthenticatedSessionController@store
  GET|HEAD        smtv.test/login ...................................................................................... login › Auth\LoginController@showLoginForm
  POST            smtv.test/login ...................................................................................................... Auth\LoginController@login
  POST            logout ........................................................................ logout › Laravel\Fortify › AuthenticatedSessionController@destroy
  GET|HEAD        smtv.test/logout .................................................................................................... Auth\LoginController@logout
  POST            smtv.test/logout .................................................................................................... Auth\LoginController@logout

and these Fortify routes

GET|HEAD        smtv.test/two-factor ...................................................................... two-factor.login › Auth\LoginController@showTwoFactor
  POST            smtv.test/two-factor ............................................................ Laravel\Fortify › TwoFactorAuthenticatedSessionController@store
  POST            two-factor-challenge ............................................................ Laravel\Fortify › TwoFactorAuthenticatedSessionController@store
  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 › ConfirmedTwoFactorAuthenticationController@store
  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@show

request data shown is

_token=SlD3LlfXjm6wGf4pbOH3s9r4lY4EGmI3c41vSao2&email=developer%40site.com&password=password-not-encrypted

app/Http/Controllers/Auth/LoginController.php has this

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/dashboard';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest', ['except' => 'logout']);
    }

    protected function loggedOut(Request $request)
    {
        return redirect('/login');
    }

    public function showTwoFactor()
    {
        return view('auth.two-factor');
    }
}

Not sure why I do not get the OTP view yet nor why I am simply sent back to the same login page.

rhand's avatar
Level 6

Hmm, I seem to be missing a method in the controller as well. The login method in app/Http/Controllers/Auth/LoginController.php

martinbean's avatar

@rhand You should have that controller at all if you’re wanting to use Fortify.

Remove the controllers from the Auth namespace.

1 like
rhand's avatar
Level 6

In the end we did manage. Main things for proper logins with and without 2FA on we did with

...
 public function register(): void
    {
        $this->app->instance(LoginResponse::class, new class implements LoginResponse {
            public function toResponse($request)
            {
                return redirect('/dashboard');
            }
        });

        $this->app->instance(TwoFactorLoginResponse::class, new class implements LoginResponse {
            public function toResponse($request)
            {
                return redirect('/dashboard');
            }
        });
    }
...

in app/Providers/FortifyServiceProvider.php . Solved main issues with active 2FA accounts like I had locally.. and later on remotely too of course. That and some controller modifications.

1 like

Please or to participate in this conversation.