Customizing Authentication in Laravel: Building a Custom Guard
Tags: laravel, authentication, php, webdev
Published: 2023-01-12 11:52:56

Laravel Guards are used to authenticate users and control access to different parts of the application. Guards are classes that implement the guard interface and are responsible for checking the user's credentials and determining if they should be granted access to the application.
Laravel ships with several guards out of the box, such as the session guard and the token guard, but you can also create custom guards to meet your specific needs.

> In this tutorial, I will show you how to implement a custom guard in laravel whereby creating an Admin guard that authenticates users to the admin Dashboard.

## 1. Environment Setup
First, You'll need to install laravel on your local machine or a development environment. here are a few resources you can use for the installations

- 
[Install laravel on your local machine](https://laravel.com/docs/9.x/installation)

- 
[Set up laravel with docker compose](https://www.digitalocean.com/community/tutorials/how-to-install-and-set-up-laravel-with-docker-compose-on-ubuntu-22-04)

If you already have laravel and composer running on your local machine then you can go ahead to create a new project.
 
```
composer create-project laravel/laravel example-app
```
Here is an image of how a running laravel project will look on your browser after all the installations have been done

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/310pdmsnozb7xf24p3hs.png)


## 2. Creating the Admin Model, Migration and Controller
With an up an running laravel project, the next step is to create the admin model, migration and controller. 

Admin Model and  Migration:

```
php artisan make:model Admin -m
```
the code above creates two files, the Admin model and the Admin migration

Admin Controller:

```
php artisan make:controller AdminController
```

## 3. Structure the Admin Table for Migration
After creating the admin migration class, the next step is to add the columns needed for the admin table. Your admin migration class should look like this:

```
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('admins', function (Blueprint $table) {
            $table->id();
            $table->string('name')->nullable();
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('admins');
    }
};

```
Now you'll need to run the migrations:

```
php artisan migrate
```

## 4. Seed Data to the Admin Table
After your migrations have been executed successfully,  A table called `admins` will be created in your database and we will have to seed data into the table.

> Laravel includes the ability to seed your database with data using seed classes. All seed classes are stored in the database/seeders directory. By default, a DatabaseSeeder class is defined for you. From this class, you may use the call method to run other seed classes, allowing you to control the seeding order.
Read more about laravel seeding on Laravel's official Documentation:
[Laravel Database Seeding](https://laravel.com/docs/9.x/seeding)

Admin Seeder:

```
php artisan make:seed AdminSeeder
```
The code above creates an ` Admin Seeder` class which will be updated to look like this:

```
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use DB;

class AdminSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
         DB::table('admins')->delete();
        $adminRecords = [
            [
                'id'=>1,'name'=>'Nerdy Gedoni','email'=>'superadmin@admin.online','password'=>'$2y$10$IK0Vz4QUjPIRszOIjfRvJO9PlbHB7kY6fRhJGFUIKNdxf.I3iW/ry'
            ]
          ];

          DB::table('admins')->insert($adminRecords);
    }
}

```

> Note: The admin password was hashed with laravel tinker : [hash password with laravel tinker](https://imwz.io/laravel-tinker-new-bcrypt-password/) 

For our seeding to work we have to call our `AdminSeeder` in the `DatabaseSeeder.php` file.

Head over to `\database\seeder\DatabaseSeeder.php` file and call our `AdminSeeder` like this:

```
<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(AdminSeeder::class);
    }
}

```

Seed Data:

```
php artisan db:seed
```
 

## 5. Create Admin Middleware And Guard
Laravel includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to your application's login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.

Create Admin Middleware:

```
php artisan make:middleware Admin
```
Our Admin middleware will verify if a user(Admin) is authenticated. If the users is not it will redirect the user to the admin login route. Update your middleware to look like this: 

```
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Auth;

class Admin
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        if (!Auth::guard('admin')->check()) {
            return Redirect('/admin');
        }
        return $next($request);
    }
}
```
Now head over to our kernel.php file `app\http\kernel.php` and plug in the admin middleware class:

```
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \App\Http\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'admin' => \App\Http\Middleware\Admin::class,
    ];
```
Next we have to create the Admin Guard in our `config\auth.php` file:

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

        //guard for the admin login
           'admin' => [
            'driver' => 'session',
            'provider' => 'admins', //admin table
        ],
    ],


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

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


```

## 6. Customize Routes
Our updated route `routes\web.php` will look like this:

```
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AdminController;


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

Auth::routes();

Route::prefix('/admin')->namespace('Admin')->group(function (){
 
    Route::get('/', [AdminController::class, 'login']);
    Route::post('login', [AdminController::class, 'loginAdmin'])->name('admin.login');

	Route::group(['middleware'=>['admin']],function ()
	{
		Route::get('dashboard', [AdminController::class, 'dashboard'])->name('admin.dashboard');
		Route::post('logout', [AdminController::class, 'logout'])->name('admin.logout');

	});
});

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

```

## 7. Update the Admin Model And Controller
Now we have completed the routes in the `web.php` file we head over to the admin model and update it to something like this:

```
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
//imported
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable
{
    use HasFactory;

    protected $guard = 'admin';

    protected $fillable = [
        'name',
        'email',
        'password',
        'created_at',
        'updated_at',
        
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

}

```
Finally update your AdminController to this:

```
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;
use Auth;

class AdminController extends Controller
{
     public function login(){
        return view('admin.login');
    }

    public function loginAdmin(Request $request){

          if (Auth::guard('admin')->attempt(['email'=>$request['email'], 'password'=>$request['password']])) {
            return Redirect('admin/dashboard')->with('message', 'Logged In Successfully...');
        }
        else{
            return Redirect()->back()->with('message', 'Invalid Login Details');
        }
    }

    public function dashboard()
    {
        return view('admin.dashboard');
    }


    public function logout()
    {
        Auth::guard('admin')->logout();
        return Redirect('/admin')->with('message', 'Logged Out Successfully... ');
    }
}

```

The AdminController has four methods, `login`, `loginAdmin`, `dashboard`, `logout`.
`login`: returns the admin login view
`loginAdmin`: Authenticates and return the admin dashboard
`dashboard`: returns the admin dashboard
`logout`: log's out the admin


## 8. Creating the Login And Dashboard Views
Finally we will create the admin login and admin dashboard in thee views folder `resources\views\admin\`. Our structure should look like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7h85clnolylat797mkxp.png)

Now update the respective views.

Login:

```
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<!-- CSRF Token -->
		<meta name="csrf-token" content="{{ csrf_token() }}">
		<title>{{ config('app.name', 'Laravel') }}</title>
		<!-- Fonts -->
		<link rel="dns-prefetch" href="//fonts.gstatic.com">
		<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
		<!-- Scripts -->
		@vite(['resources/sass/app.scss', 'resources/js/app.js'])
	</head>
	<body>
		<div id="app">
			<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
				<div class="container">
					<a class="navbar-brand" href="{{ url('/') }}">
						{{ config('app.name', 'Laravel') }}
					</a>
					<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
					<span class="navbar-toggler-icon"></span>
					</button>
					<div class="collapse navbar-collapse" id="navbarSupportedContent">
						<!-- Left Side Of Navbar -->
						<ul class="navbar-nav me-auto">
						</ul>
						<!-- Right Side Of Navbar -->
						<ul class="navbar-nav ms-auto">
							
						</ul>
					</div>
				</div>
			</nav>
			<main class="py-4">
				<div class="container">
					<div class="row justify-content-center">
						<div class="col-md-8">
							<div class="card">
								<div class="card-header">{{ __('Admin Login') }}</div>
								<div class="card-body">
									<form method="POST" action="{{ route('admin.login') }}">
										@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" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
												
											</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" name="password" required autocomplete="current-password">
												
											</div>
										</div>
										
										<div class="row mb-0">
											<div class="col-md-8 offset-md-4">
												<button type="submit" class="btn btn-primary">
												{{ __('Login') }}
												</button>
												
											</div>
										</div>
									</form>
								</div>
							</div>
						</div>
					</div>
				</div>
			</main>
		</div>
	</body>
</html>
```
Dashboard:

```
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<!-- CSRF Token -->
		<meta name="csrf-token" content="{{ csrf_token() }}">
		<title>{{ config('app.name', 'Laravel') }}</title>
		<!-- Fonts -->
		<link rel="dns-prefetch" href="//fonts.gstatic.com">
		<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
		<!-- Scripts -->
		@vite(['resources/sass/app.scss', 'resources/js/app.js'])
	</head>
	<body>
		<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
			<div class="container">
				<a class="navbar-brand" href="{{ url('/') }}">
					{{ config('app.name', 'Laravel') }}
				</a>
				<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
				<span class="navbar-toggler-icon"></span>
				</button>
				<div class="collapse navbar-collapse" id="navbarSupportedContent">
					<!-- Left Side Of Navbar -->
					<ul class="navbar-nav me-auto">
						
					</ul>
					<!-- Right Side Of Navbar -->
					<ul class="navbar-nav ms-auto">
						<li>
							<a class="nav-link"href="{{ route('admin.logout') }}"
								onclick="event.preventDefault();
								document.getElementById('logout-form').submit();">
								{{ __('Logout') }}
							</a>
							<form id="logout-form" action="{{ route('admin.logout') }}" method="POST" class="d-none">
								@csrf
							</form>
						</li>
					</ul>
				</div>
			</div>
		</nav>
		<main class="py-4">
			<div class="container">
				<div class="row justify-content-center">
					<div class="col-md-8">
						<div class="card">
							<div class="card-header">Welcome Admin!!!</div>
						</div>
					</div>
				</div>
			</div>
		</main>
	</body>
</html>
```
Finally, we are done. We've just created our custom guard that verifies if a user is authenticated and hereby navigates the authenticated user to the admin section of our application.

Url: `localhost\admin`: 
Returns the admin login page for authentication

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/merho79z7wv21ssxni77.png)

Url: `localhost\admin\dashboard`: 
After user authenticated, returns dashboard

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nipvy916zry425m86j35.png)

## Where Next!
After this extensive tutorial, you might want to dive deeper into laravel authentications in order to secure your application in every possible way as well as implement more complex actions

Quick Links:
[Laravel Documentaion](https://laravel.com/docs/9.x)
[Authentication](https://www.tutorialspoint.com/laravel/laravel_authentication.htm)

<a href="https://www.buymeacoffee.com/gedoni" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-green.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 217px !important;" ></a>