How to Implement Multiple Authentication Guards in Laravel 9

Smit Pipaliya
TechvBlogs
Published in
14 min readAug 28, 2022

Aug 16, 2022, Originally published at techvblogs.com ・14 min read

Eventually, we are going to learn how to create multiple auth (Authentication) in Laravel using guards, and we will also consider other laravel imperatives that are useful to build laravel basic auth app from starting.

In general, Authentication is the security process, and it indicates acknowledging the genuine user with proper account details.

We will develop two users one is an admin, and the other one is a regular user. Based on their roles, we will allow them to access in the app using middleware. Let us begin our work and start building our application.

How to Implement Multiple Authentication Guards in Laravel 9

Before starting the development, let us first see the video on how multi-guard authentication works. In our demo, we used two logins, one for the blogger and one for the admin.

Step 1: Install Laravel Project

First, open Terminal and run the following command to create a fresh laravel project:

composer create-project --prefer-dist laravel/laravel multi-guard-auth

or, if you have installed the Laravel Installer as a global composer dependency:

laravel new multi-guard-auth

Step 2: Install Laravel UI

Next, you need to run the below command in your terminal

composer require laravel/ui

Step 3: Setup Auth Scaffolding with Bootstrap 5

php artisan ui bootstrap --auth

Step 4: Install NPM Dependencies

Run the following command to install frontend dependencies:

npm install

After the Update Of Laravel 9.20.0, Laravel replaces Webpack with Vite. You can follow this article for Setup Bootstrap 5 in Laravel 9.

Step 5. Configure Database Details:

After, Installation Go to the project root directory, open the .env file, and set database detail as follow:

DB_CONNECTION=mysql 
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=<DATABASE NAME>
DB_USERNAME=<DATABASE USERNAME>
DB_PASSWORD=<DATABASE PASSWORD>

Step 6: Create Migration and Model

Use the below command to create migration and Model for Admin and Blog

php artisan make:model Admin -m

-m the argument is used to create a migration file.

Update the migration file of the admin like the users’ migration table, or you can also extend the table by adding fields needed for the application.

Step 7: Define Guards

Guards define how the admin is authenticated for each request. Our application will have a guard Admin. after defining the guards set their providers. When we use these guards it tells what to use for authentication or validation. Open the Admin.php file in the app/Models folder. Update guard as admin and fillable array with field names.

<?phpnamespace App\Models;use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Model;
class Admin extends Authenticatable
{
use HasFactory;
protected $guard = "admin"; protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
}

Open config/auth.php for adding guard. We’ve added one guard: admin and updated their provider’s array.

<?phpreturn [    /*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model/table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Models\Admin::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
'admins' => [
'provider' => 'admins',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the number of seconds before a password confirmation
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => 10800,];

Step 8: Set Up Controller

Open the LoginController in app/Http/Controllers/Auth and edit as follows:

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
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 = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
$this->middleware('guest:admin')->except('logout');
}
}

We set the middleware to restrict access to this controller or its methods. It is important we defined all the different types of guests in the controller. This way, if one type of user is logged in and you try to use another user type to log in, it will redirect you to a predefined authentication page.

Now, define the login for admins:

app/Http/Controllers/Auth/LoginController.php

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
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 = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
$this->middleware('guest:admin')->except('logout');
}
public function showAdminLoginForm()
{
return view('auth.login', ['url' => route('admin.login-view'), 'title'=>'Admin']);
}
public function adminLogin(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|min:6'
]);
if (\Auth::guard('admin')->attempt($request->only(['email','password']), $request->get('remember'))){
return redirect()->intended('/admin/dashboard');
}
return back()->withInput($request->only('email', 'remember'));
}
}

We have set up a method to return the login page for an admin. We will use the same page for all the user types and only change the URL they get sent to. Saves us a lot of code we could avoid writing.

We also defined the adminLogin the method which checks that the right credentials are supplied. Then we attempt to log a user in with the admin guard. It is important we set this guard when attempting a login so that the Auth facade will check the right table matching credentials. It will also set up our authentication so we can restrict pages based on the type of user who is logged in.

Step 9: Modify RegisterController

Open the RegisterController and edit as follows:

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers; /**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
$this->middleware('guest:admin');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}

We have set up the middleware the controller will use, just like we did with the LoginController.

Now, let us set up the methods to return the registration pages for the admin:

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers; /**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
$this->middleware('guest:admin');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
public function showAdminRegisterForm()
{
return view('auth.register', ['route' => route('admin.register-view'), 'title'=>'Admin']);
}
}

Now, we can define our methods for creating an admin:

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\User;
use App\Models\Admin;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Request;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers; /**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
$this->middleware('guest:admin');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
public function showAdminRegisterForm()
{
return view('auth.register', ['route' => route('admin.register-view'), 'title'=>'Admin']);
}
protected function createAdmin(Request $request)
{
$this->validator($request->all())->validate();
$admin = Admin::create([
'name' => $request['name'],
'email' => $request['email'],
'password' => Hash::make($request['password']),
]);
return redirect()->intended('admin');
}
}

Step 10: Set up authentication pages

Open the resources/views/auth/login.blade.php file and edit as follows:

@extends('layouts.app')@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ $title ?? "" }} {{ __('Login') }}</div>
<div class="card-body">
@isset($route)
<form method="POST" action="{{ $route }}">
@else
<form method="POST" action="{{ route('login') }}">
@endisset

@csrf
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

We are checking if we passed a route parameter to the page when we called it. If we did, we modify the form's action to use the route parameter. We also modified the header of the form so that it shows the type of user based on their login parameter.

Open the resources/views/auth/register.blade.php file and edit as follows:

@extends('layouts.app')@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ $title ?? "" }} {{ __('Register') }}</div>
<div class="card-body">
@isset($route)
<form method="POST" action="{{ $route }}">
@else
<form method="POST" action="{{ route('register') }}">
@endisset
@csrf
<div class="row mb-3">
<label for="name" class="col-md-4 col-form-label text-md-end">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password-confirm" class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

We replicated what we did for the login page here.

Step 11: Create the pages for authenticated users

Now that we are done setting up the login and register page, let us make the page the admin will see when they are authenticated. Open the terminal and run the following commands to create a new file. Next, we will insert the corresponding code snippets into the files.

touch resources/views/admin.blade.php

Next, insert this code block into the resources/views/admin.blade.php file:

@extends('layouts.app')@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Dashboard') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
Hello Admin!
</div>
</div>
</div>
</div>
</div>
@endsection

Finally, open the resources/views/home.blade.php file and replace with the following:

@extends('layouts.app')@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Dashboard') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
Hello User!
</div>
</div>
</div>
</div>
</div>
@endsection

Step 12: Set up the routes

Let us define the routes to access all the pages we have created so far. Open the routes/web.php file and replace with the following:

<?phpuse Illuminate\Support\Facades\Route;/*
|--------------------------------------------------------------------------
| 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!
|
*/
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\RegisterController;
Route::get('/', function () {
return view('welcome');
});
Auth::routes();Route::get('/admin',[LoginController::class,'showAdminLoginForm'])->name('admin.login-view');
Route::post('/admin',[LoginController::class,'adminLogin'])->name('admin.login');
Route::get('/admin/register',[RegisterController::class,'showAdminRegisterForm'])->name('admin.register-view');
Route::post('/admin/register',[RegisterController::class,'createAdmin'])->name('admin.register');
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('/admin/dashboard',function(){
return view('admin');
})->middleware('auth:admin');

Step 13: Modify RedirectIfAuthenticated.php Middleware

It is important you modify how users are redirected when they are authenticated. Laravel by default redirects all authenticated users to /home. We will get the error below if we do not modify the redirection.

open the app/Http/Controllers/Middleware/RedirectIfAuthenticated.php file and replace with this:

<?phpnamespace App\Http\Middleware;use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @param string|null ...$guards
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) { if ($guard == "admin" && Auth::guard($guard)->check()) {
return redirect('/admin/dashboard');
}
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

The RedirectIfAuthenticated middleware receives the auth guard as a parameter. This middleware is triggered when we try to visit any page meant for authenticated users.

Step 14: Modify authentication exception handler

Open the handler file app/Exceptions/Handler.php and add the following:

<?phpnamespace App\Exceptions;use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Auth\AuthenticationException;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
//
];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
}
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['message' => 'Unauthenticated.'], 401);
}
if ($request->is('admin') || $request->is('admin/*')) {
return redirect()->guest('/admin');
}

return redirect()->guest(route('login'));
}
}

The unauthenticated the method we just added resolves this issue we have. It receives an AuthenticationExpection exception by default which carries that guard information.

Our workaround is to use request->is(). This checks the URL we are trying to access. It can also check the URL pattern if we do not have an absolute URL or if we have a route group.

In our case, we first check if we received a JSON request and handle the exception separately. Then we check if we are trying to access /admin any URL preceded by admin. We redirect the user to the appropriate login page.

This is a good workaround for us, but it means we must know the absolute URL we want to access, or at least have the same prefix for all routes that will be protected by our guard.

Step 15: Run the application

Now that our application is ready, run the following command to get it up:

php artisan serve

Most Probably Application URL will be http://localhost:8000.

Remember to visit http://localhost:8000/admin and http://localhost:8000/admin/register.

Conclusion

In this article, we dived deep into Laravel authentication. We defined multiple guards to handle multiple authentications and access control. We also handle redirection for authenticated users and redirection for unauthenticated users.

The source code to the application in this article is available on Github.

Thank you for reading this blog.

--

--

Smit Pipaliya
TechvBlogs

I am Project Manager at ServerAvatar Cloud Technology.