DEV Community

Cover image for Customizing Authentication in Laravel: Building a Custom Guard
Nerdy Gedoni
Nerdy Gedoni

Posted on

Customizing Authentication in Laravel: Building a Custom Guard

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

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
Enter fullscreen mode Exit fullscreen mode

Here is an image of how a running laravel project will look on your browser after all the installations have been done

Image description

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
Enter fullscreen mode Exit fullscreen mode

the code above creates two files, the Admin model and the Admin migration

Admin Controller:

php artisan make:controller AdminController
Enter fullscreen mode Exit fullscreen mode

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');
    }
};

Enter fullscreen mode Exit fullscreen mode

Now you'll need to run the migrations:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

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

Admin Seeder:

php artisan make:seed AdminSeeder
Enter fullscreen mode Exit fullscreen mode

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);
    }
}

Enter fullscreen mode Exit fullscreen mode

Note: The admin password was hashed with laravel tinker : hash password with laravel tinker

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);
    }
}

Enter fullscreen mode Exit fullscreen mode

Seed Data:

php artisan db:seed
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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,
    ];
Enter fullscreen mode Exit fullscreen mode

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,
        ],
    ],


Enter fullscreen mode Exit fullscreen mode

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');

Enter fullscreen mode Exit fullscreen mode

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',
    ];

}

Enter fullscreen mode Exit fullscreen mode

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... ');
    }
}

Enter fullscreen mode Exit fullscreen mode

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

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

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

Image description

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
Authentication

Buy Me A Coffee

Top comments (0)