Laravel Fortify Multi Auth

Step 1:

Installation of Laravel Livewire and Jetstream Package

For you to install jetstream and laravel together run the following command

laravel new laravelmultiauth --jet

Once the installation is done run the following

 npm install && npm run dev 

create a database, you can give it a name you prefer and run the following command to migrate the tables

php artisan migrate

and finally, run the following command php artisan serve to run the application in your local environment

Step 2:

Admins Table and Migration

Start by creating an admin controller, so run this command

php artisan make:controller AdminController

Let’s also create a model and a migration file table for admin by running this command

php artisan make:model Admin -m

copy the contents the of user table to admins since we are creating a similar authentication for admin. So you should

have something like this in the admin’s table

//…_create_admins_table.php

Schema::create('admins', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->foreignId('current_team_id')->nullable();
$table->string('profile_photo_path', 2048)->nullable();
$table->timestamps();
});

as well as copy the contents of the user.php model to the admin.php model therefore you should have something like this…

Admin.php

class Admin extends Authenticatable
{
use HasApiTokens;
use HasFactory;
use HasProfilePhoto;
use Notifiable;
use TwoFactorAuthenticatable;
/**
 * The attributes that are mass assignable.
 *
 * @var string[]
 */
protected $fillable = [
    'name',
    'email',
    'password',
];

/**
 * The attributes that should be hidden for serialization.
 *
 * @var array
 */
protected $hidden = [
    'password',
    'remember_token',
    'two_factor_recovery_codes',
    'two_factor_secret',
];

/**
 * The attributes that should be cast.
 *
 * @var array
 */
protected $casts = [
    'email_verified_at' => 'datetime',
];

/**
 * The accessors to append to the model's array form.
 *
 * @var array
 */
protected $appends = [
    'profile_photo_url',
];

}

good!. Now run this command //php artisan migrate to migrate the admin’s table we just created earlier.

Step 3:

Seed Admins Data

Now here we will create an admin factory that will help us to seed data into the admin’s table.

Learn more about factories in laravel here. Ok, run this command php artisan make:factory AdminFactory.

After that copy the items from UserFactory.php to the newly created AdminFactory.php and change the first two fields’ name and email so the output should look like this

AdminFactory.php

'name' => 'Admin',
'email' => 'admin@admin.com',
'email_verified_at' => now(),
'password' =>  bcrypt('password123'), // password
'remember_token' => Str::random(10),

then don’t forget to add //use Illuminate\Support\Str; from the UserFactory.php to AdminFactory.php since we are using the //Str::random for remembering token.

Lastly, go to seeders -> DatabaseSeeder.php and change the following in the public function run

\App\Models\User::factory(10)->create(); to \App\Models\Admin::factory()->create();

so that it will call the Admin model we created earlier and seed our admins’ table. So now run the command

php artisan migrate --seed

Step 4:

Guards For Admins

In this step, we will create guards for admins, so go to the config and locate the auth.php and update like below

Authentication Guards:

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],

and also update the providers

User Providers:

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],

for passwords update them as follows

Resetting Passwords:

'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
'admins' => [
'provider' => 'admins',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],

Once that is done. Open this controller //AuthenticatedSessionController.php. This controller manages the guards i.e the

login and logout session for your app, currently, we are using the web guard. So I want us to update it to use admin guard too.

You can check which guard the controller is using from the constructor by //dd($this->guard) it will give you the default

guard your application is using.

public function __construct(StatefulGuard $guard)
{
$this->guard = $guard;
dd($this->guard); // default guard is the web guard
}
Ex:
Illuminate\Auth\SessionGuard {#415 ▼
#name: "web"
#lastAttempted: null
#viaRemember: false
#rememberDuration: 2628000
#session: Illuminate\Session\Store {#424 ▶}
#cookie: Illuminate\Cookie\CookieJar {#425 ▶}
#request: Illuminate\Http\Request {#43 ▶}
#events: Illuminate\Events\Dispatcher {#27 ▶}
#loggedOut: false
#recallAttempted: false
#user: null
#provider: Illuminate\Auth\EloquentUserProvider {#410 ▶}

Now go to this file Providers->FortifyServiceProvider.php and register the following classes

AdminController.php from the controller, RedirectIfTwoFactorAuthenticatable::class and AttemptToAuthenticate::class from AuthenticatedSessionController.php

so that when the controller runs it can also load the other sessions for the admin

FortifyServiceProvider.php

….
public function register()
{
$this->app->when([AdminController::class, AttemptToAuthenticate::class,public function register()
{
$this->app->when([AdminController::class, AttemptToAuthenticate::class,
    RedirectIfTwoFactorAuthenticatable::class]);
}

….

You also need to update the same function with the statefulguard from AuthenticatedSessionController.php and return the
guard for admin like this

FortifyServiceProvider.php

….
public function register()
{
$this->app->when([AdminController::class, AttemptToAuthenticate::class,
    RedirectIfTwoFactorAuthenticatable::class])

    ->needs(StatefulGuard::class)

    ->give(function() {

        return Auth::guard('admin');

    });
} 
….

since we are using the above classes you need to use the following lines of code i.e copy the following from

AuthenticatedSessionController.php to FortifyServiceProvider.php

use App\Actions\Fortify\AttemptToAuthenticate;
use App\Actions\Fortify\RedirectIfTwoFactorAuthenticatable;
use Illuminate\Contracts\Auth\StatefulGuard;
use App\Http\controllers\AdminController;

Once it’s done I want to create a statefulguard for admin just like we have for users. So go to the app make a folder rename

it app->Guards->AdminStatefulGuard.php in this file copy the contents from the normal statefulguard to AdminStatefulGuard.php

AdminStatefulGuard.php

<?php
namespace App\Guards; // change like this

interface AdminStatefulGuard extends Guard // change like this
{
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @return bool
*/
public function attempt(array $credentials = [], $remember = false);
/**
 * Log a user into the application without sessions or cookies.
 *
 * @param  array  $credentials
 * @return bool
 */
public function once(array $credentials = []);

/**
 * Log a user into the application.
 *
 * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
 * @param  bool  $remember
 * @return void
 */
public function login(Authenticatable $user, $remember = false);

/**
 * Log the given user ID into the application.
 *
 * @param  mixed  $id
 * @param  bool  $remember
 * @return \Illuminate\Contracts\Auth\Authenticatable|bool
 */
public function loginUsingId($id, $remember = false);

/**
 * Log the given user ID into the application without sessions or cookies.
 *
 * @param  mixed  $id
 * @return \Illuminate\Contracts\Auth\Authenticatable|bool
 */
public function onceUsingId($id);

/**
 * Determine if the user was authenticated via "remember me" cookie.
 *
 * @return bool
 */
public function viaRemember();

/**
 * Log the user out of the application.
 *
 * @return void
 */
public function logout();
}

Step 5:

In this step, we start by copying the contents of AuthenticatedSessionController.php to AdminController and editing a few things and it should look like this.

AdminController.php

<?php

namespace App\Http\Controllers; // remember to update the namespace like this

use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Routing\Pipeline;
use App\Actions\Fortify\AttemptToAuthenticate;// update to this
use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled;
use Laravel\Fortify\Actions\PrepareAuthenticatedSession;
use App\Actions\Fortify\RedirectIfTwoFactorAuthenticatable; // update to this
//use Laravel\Fortify\Contracts\LoginResponse;
use App\Http\Responses\LoginResponse; // change to this, will create later
use Laravel\Fortify\Contracts\LoginViewResponse;
use Laravel\Fortify\Contracts\LogoutResponse;
use Laravel\Fortify\Features;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Http\Requests\LoginRequest;

class AdminController extends Controller
{
/**
* The guard implementation.
*
* @var \Illuminate\Contracts\Auth\StatefulGuard
*/
protected $guard;
/**
 * Create a new controller instance.
 *
 * @param  \Illuminate\Contracts\Auth\StatefulGuard  $guard
 * @return void
 */
public function __construct(StatefulGuard $guard)
{
    $this->guard = $guard;
}

/**
 * Show the login view.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Laravel\Fortify\Contracts\LoginViewResponse
 */
public function create(Request $request): LoginViewResponse
{
    return app(LoginViewResponse::class);
}

/**
 * Attempt to authenticate a new session.
 *
 * @param  \Laravel\Fortify\Http\Requests\LoginRequest  $request
 * @return mixed
 */
public function store(LoginRequest $request)
{
    return $this->loginPipeline($request)->then(function ($request) {
        return app(LoginResponse::class);
    });
}

/**
 * Get the authentication pipeline instance.
 *
 * @param  \Laravel\Fortify\Http\Requests\LoginRequest  $request
 * @return \Illuminate\Pipeline\Pipeline
 */
protected function loginPipeline(LoginRequest $request)
{
    if (Fortify::$authenticateThroughCallback) {
        return (new Pipeline(app()))->send($request)->through(array_filter(
            call_user_func(Fortify::$authenticateThroughCallback, $request)
        ));
    }

    if (is_array(config('fortify.pipelines.login'))) {
        return (new Pipeline(app()))->send($request)->through(array_filter(
            config('fortify.pipelines.login')
        ));
    }

    return (new Pipeline(app()))->send($request)->through(array_filter([
        config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class,
        Features::enabled(Features::twoFactorAuthentication()) ? RedirectIfTwoFactorAuthenticatable::class : null,
        AttemptToAuthenticate::class,
        PrepareAuthenticatedSession::class,
    ]));
}

/**
 * Destroy an authenticated session.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Laravel\Fortify\Contracts\LogoutResponse
 */
public function destroy(Request $request): LogoutResponse
{
    $this->guard->logout();

    $request->session()->invalidate();

    $request->session()->regenerateToken();

    return app(LogoutResponse::class);
}
}

Now go to the web.php route and update it as follows…

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AdminController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
return view('welcome');
});

Route::group(['prefix' =>'admin','middleware'=>['admin:admin']],function() {
Route::get('/login', [AdminController::class, 'loginform']);
Route::post('/login', [AdminController::class, 'store'])->name('admin.login');
});

Route::middleware(['auth:sanctum, admin', 'verified'])->get('/admin/dashboard', function () {
return view('dashboard');
})->name('dashboard');

Route::middleware(['auth:sanctum, web', 'verified'])->get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');

after this create a guard in AdminController.php below (public function construct) for admin using the login form method and it should be like this

AdminController.php

public function loginform()
{
return view('auth.login',['guard' => 'admin']);
}

and then since we want to return a login blade like the default one we have for the user. Go to loginblade.php for user and update it as follows

loginblade.php


<form method="POST" action="{{ isset($guard) ? url($guard. '/login') : 
            route('login') }}">
            @csrf

what this does will either redirect you to the default user normal login page or if you append it with admin like

/admin/login will take you to the admin login blade.

The next step is to copy these two files from …vendor/laravel/fortify/src/actions/redirectiftwofactorauthenticatable.php and

…vendor/laravel/fortify/src/actions/attempttoauthenticate.php to app/actions/fortify/… and edit them as below

AttemptToAuthenticate.php

<?php

namespace App\Actions\Fortify; // update the namespace only

use Illuminate\Auth\Events\Failed;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\LoginRateLimiter;

class AttemptToAuthenticate
{
/**
* The guard implementation.
*
* @var \Illuminate\Contracts\Auth\StatefulGuard
*/
protected $guard;

/**
 * The login rate limiter instance.
 *
 * @var \Laravel\Fortify\LoginRateLimiter
 */
protected $limiter;

/**
 * Create a new controller instance.
 *
 * @param  \Illuminate\Contracts\Auth\StatefulGuard  $guard
 * @param  \Laravel\Fortify\LoginRateLimiter  $limiter
 * @return void
 */
public function __construct(StatefulGuard $guard, LoginRateLimiter $limiter)
{
    $this->guard = $guard;
    $this->limiter = $limiter;
}

/**
 * Handle the incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  callable  $next
 * @return mixed
 */
public function handle($request, $next)
{
    if (Fortify::$authenticateUsingCallback) {
        return $this->handleUsingCustomCallback($request, $next);
    }

    if ($this->guard->attempt(
        $request->only(Fortify::username(), 'password'),
        $request->boolean('remember'))
    ) {
        return $next($request);
    }

    $this->throwFailedAuthenticationException($request);
}

/**
 * Attempt to authenticate using a custom callback.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  callable  $next
 * @return mixed
 */
protected function handleUsingCustomCallback($request, $next)
{
    $user = call_user_func(Fortify::$authenticateUsingCallback, $request);

    if (! $user) {
        $this->fireFailedEvent($request);

        return $this->throwFailedAuthenticationException($request);
    }

    $this->guard->login($user, $request->boolean('remember'));

    return $next($request);
}

/**
 * Throw a failed authentication validation exception.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return void
 *
 * @throws \Illuminate\Validation\ValidationException
 */
protected function throwFailedAuthenticationException($request)
{
    $this->limiter->increment($request);

    throw ValidationException::withMessages([
        Fortify::username() => [trans('auth.failed')],
    ]);
}

/**
 * Fire the failed authentication attempt event with the given arguments.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return void
 */
protected function fireFailedEvent($request)
{
    event(new Failed(config('fortify.guard'), null, [
        Fortify::username() => $request->{Fortify::username()},
        'password' => $request->password,
    ]));
}
}

RedirectIfTwoFactorAuthenticatable.php

<?php

namespace App\Actions\Fortify; // update the namespace only

use Illuminate\Auth\Events\Failed;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\LoginRateLimiter;
use Laravel\Fortify\TwoFactorAuthenticatable;

class RedirectIfTwoFactorAuthenticatable
{
/**
* The guard implementation.
*
* @var \Illuminate\Contracts\Auth\StatefulGuard
*/
protected $guard;

/**
 * The login rate limiter instance.
 *
 * @var \Laravel\Fortify\LoginRateLimiter
 */
protected $limiter;

/**
 * Create a new controller instance.
 *
 * @param  \Illuminate\Contracts\Auth\StatefulGuard  $guard
 * @param  \Laravel\Fortify\LoginRateLimiter  $limiter
 * @return void
 */
public function __construct(StatefulGuard $guard, LoginRateLimiter $limiter)
{
    $this->guard = $guard;
    $this->limiter = $limiter;
}

/**
 * Handle the incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  callable  $next
 * @return mixed
 */
public function handle($request, $next)
{
    $user = $this->validateCredentials($request);

    if (optional($user)->two_factor_secret &&
        in_array(TwoFactorAuthenticatable::class, class_uses_recursive($user))) {
        return $this->twoFactorChallengeResponse($request, $user);
    }

    return $next($request);
}

/**
 * Attempt to validate the incoming credentials.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return mixed
 */
protected function validateCredentials($request)
{
    if (Fortify::$authenticateUsingCallback) {
        return tap(call_user_func(Fortify::$authenticateUsingCallback, $request), function ($user) use ($request) {
            if (! $user) {
                $this->fireFailedEvent($request);

                $this->throwFailedAuthenticationException($request);
            }
        });
    }

    $model = $this->guard->getProvider()->getModel();

    return tap($model::where(Fortify::username(), $request->{Fortify::username()})->first(), function ($user) use ($request) {
        if (! $user || ! $this->guard->getProvider()->validateCredentials($user, ['password' => $request->password])) {
            $this->fireFailedEvent($request, $user);

            $this->throwFailedAuthenticationException($request);
        }
    });
}

/**
 * Throw a failed authentication validation exception.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return void
 *
 * @throws \Illuminate\Validation\ValidationException
 */
protected function throwFailedAuthenticationException($request)
{
    $this->limiter->increment($request);

    throw ValidationException::withMessages([
        Fortify::username() => [trans('auth.failed')],
    ]);
}

/**
 * Fire the failed authentication attempt event with the given arguments.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Illuminate\Contracts\Auth\Authenticatable|null  $user
 * @return void
 */
protected function fireFailedEvent($request, $user = null)
{
    event(new Failed(config('fortify.guard'), $user, [
        Fortify::username() => $request->{Fortify::username()},
        'password' => $request->password,
    ]));
}

/**
 * Get the two factor authentication enabled response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  mixed  $user
 * @return \Symfony\Component\HttpFoundation\Response
 */
protected function twoFactorChallengeResponse($request, $user)
{
    $request->session()->put([
        'login.id' => $user->getKey(),
        'login.remember' => $request->filled('remember'),
    ]);

    return $request->wantsJson()
                ? response()->json(['two_factor' => true])
                : redirect()->route('two-factor.login');
}
}

Step 6:

In this step, we start by updating the RouteServiceprovider.php since it has the guard for users with admin as well.

So go to Providers->RouteServiceProvider.php and update it as follows

RouteServiceProvider.php

public const HOME = '/dashboard';

public static function redirectTo($guard){
    return $guard. '/dashboard';
}

then go to middleware->RedirectIfAuthenticated.php and update it with return $guard. ‘/dashboard’ as follows

RedirectIfAuthenticated.php

public function handle(Request $request, Closure $next, …$guards)
{

$guards = empty($guards) ? [null] : $guards;

 foreach ($guards as $guard) {
        if (Auth::guard($guard)->check()) {
            return redirect($guard. '/dashboard'); // change like this
        }
    }

    return $next($request);
}
}

Now create another middleware for admin, so here you will copy and change the RedirectIfAuthenticated.php to

AdminRedirectIfAuthenticated.php for admin and edit some few things. What you need to do is to copy and paste the contents

from RedirectIfAuthenticated.php to AdminRedirectIfAuthenticated.php and finally register the middleware.

AdminRedirectIfAuthenticated.php

<?php

namespace App\Http\Middleware;

use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AdminRedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null …$guards
* @return mixed
*/

public function handle(Request $request, Closure $next, …$guards)
{
$guards = empty($guards) ? [null] : $guards;    
foreach ($guards as $guard) {
        if (Auth::guard($guard)->check()) {
            return redirect($guard. '/dashboard');
        }
    }

    return $next($request);
}
}

register the middleware in the kernel.php. So you should have something like this…

kernel.php

protected $routeMiddleware = [

'admin' => \App\Http\Middleware\AdminRedirectIfAuthenticated::class,
….
….

];

the next step is to create a LoginResponse.php just like the one for the user which redirects the admin to his page after being logged in.

Make a folder Responses under the app->http and create a LoginResponse.php then copy the code from User LoginResponse.php

into the newly created LoginResponse for admin. Do like this app->Http->Responses->LoginResponse.php

LoginResponse.php for (admin)

<?php

namespace App\Http\Responses;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
use Laravel\Fortify\Fortify;

class LoginResponse implements LoginResponseContract
{
/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function toResponse($request)
{
return $request->wantsJson()
? response()->json(['two_factor' => false])
: redirect()->intended('admin/dashboard'); change like this
}
}

This is the LoginResponse I told you we would create later. Make sure you already updated AdminController.php with this

line of code use App\Http\Responses\LoginResponse; which calls for admin to be logged in.

Now go ahead and test everything to make sure it’s working If not check the code in GitHub to verify where you might have gone wrong. Remember to use Illuminate\Support\Facades\Auth; above the FortifyServiceProvider.php for authentication if an error is thrown.

Watch this tutorial on my YouTube Channel and subscribe…

https://youtu.be/9J_4xpJt3nM
close

Sign up for free tutorials in your inbox.

We don’t spam! Read our privacy policy for more info.

Leave a Comment

Your email address will not be published.