Creating Custom Guards, Roles, and Token-Based Authorization in Laravel: A Comprehensive Guide

Assume that we want to create a guard for students and want to generate a authorization token on login.

Create table

create the table using the following command

php artisan create:migration create_students

You will find your table within app/database/migrations directory. Here we will defin the fields.

The important point to note here is in order to save tokens maped to it you need to add a rememberToken() field to it.

public function up()
    {
        Schema::create('students', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email');
            $table->string('password');
            $table->rememberToken();
        });
    }

Since we have changed the schema of the table now we need to migrate it once again. But this time you just need to run the following command

php artisan migrate

Create Model

create the Students model

php artisan create:model Student

By default your Model wil extend model class, but to make it authenticable you need to extend it by User class.

Also you need to include HasApiTokens trait. This is a package used for API authentication. It allows the associated model to issue API tokens for users, for our token-based authentication for APIs.

Now your student model should look somewhat like follows

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;

class Student extends Authenticatable
{
    use HasFactory, HasApiTokens;

    protected $primaryKey = 'id';

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

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

    public $timestamps = false;
}

Configre your custom guard

within the config/auth.php file make the following change

<?php

return [

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    // define the authentication guard
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'student' => [
            'driver' => 'session',
            'provider' => 'students',
        ],
    ],

    // define the provider that you provided in authentication guard
    // driver can be database or eloquent
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        'students' => [
            'driver' => 'eloquent',
            'model' => App\Models\Student::class,
        ],
    ],

    // In case you have multiple password reset configuration
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
        'students' => [
            'provider' => 'students',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    // password confirmation time
    'password_timeout' => 10800,

];

Define custom guard in model

Now you need to define it into your respective model for which you created the guard, in this case it is protected $guard = 'student' .

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;

class Student extends Authenticatable
{
    use HasFactory, HasApiTokens;

    protected $primaryKey = 'id';

    protected $guard = 'student'    

    //... 
}

Controller

Now let's write the logic for our login

To do so I would prefer creating a controller, where I would authenticate users. The controller I would create would be AuthenticationConroller

php artisan make:controller AuthenticationController

Here first we will include HasApiTokens trait. This will allow you to associate a model to issue API tokens. Also include Auth from Illuminate\Support\Facades\Auth

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Laravel\Sanctum\HasApiTokens;

class AuthenticationController extends Controller
{
    use HasApiTokens;

        function studentLogin(Request $request){
            // body
        }
}

Now to check user credentials there is a function in laravel, which is attempt. But this function only cheks the credentials provided in users table which is created by default. But we need to check the credentials in students table. Worry not! Remember we created guard? This is moment for which we created it. we will use the guard method where we will specifiy the student guard that we created. And now it will validate credentials on behalf of students table.

Auth::guard('student')->attempt(
                            'email' => $request->email,
                            'password' => $request->password,
                        )

If the credentials matches than it will return true else it returns false .

Next we will generate a token for the authenticated user.

// it identifies the current authenticated user
$user = Auth::guard('student')->user

// create token for that particular user
$token = $user->createToken('student_token',['Student'])->plainTextToken;

The token we are generating will have role as student and we will name it as student_token . The plainTextToken method retrives the unhashed plain-text value of the generated token. which we will return to the user so that they can send it into response header with there request to access protect routes protected by this ability.

Finally your Authentication controller should look like

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Laravel\Sanctum\HasApiTokens;

class AuthenticationController extends Controller
{
    use HasApiTokens;

        function studentLogin(Request $request){
            if(Auth::guard('student')->attempt(
                            'email' => $request->email,
                            'password' => $request->password,
                        )){
                $user = Auth::guard('student')->user();        
                $token = $user->createToken('student_token',['Student'])->plainTextToken;
                return response()->json([
                    "token" => $token,
                ],200);
            }
            return response()->json(["error"=>"invalid credentials"],401);
        }
}

You need to register users before they can login. Since we are not focusing on user registration, so I will keep it sort and simple. However for a basic registration you may include the following studentRegistration function into AuthenticationController.

function studentRegistration(Request $request){
    try{
        Student::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);
    } catch (\Throwable $th){
        return response(["ERROR" => "Please provide valid input"],401);
    }
    return response(["Message" => "User registered successfully"],200);
}

Now lets define a function that provides the name of the user using the Authorization token that is assigned. Then we will finally create the api routes for it. lets name it as studentName.

function studentName(Request $request){
    $user = Auth::user();
    return response(["name" => $user->name],200);
}

Defining Routes

The routes should be defined under routes/api.php file. It is because we are creating api routes and we want to protect and authenticate api routes.

Remember the login and register routes should not be protected by any middleware because other wise we will need to login before we login. And we don't want to do this. However in some scenarios where only admin can register students or some specific user are able to perform such task. In those cases it is prefect to buind them with some Middlewares checking if the user have special permissions or not.

Therefore for the time being we will define our routes as follows:

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

// other routes ...

Route::controller(AuthenticationController::class)->group(function(){
    Route::post('/register','studentRegistration');
    Route::post('/login','studentLogin')
});

The above code says that the routes will call the functions within the authentication controller and so it groups them by it. Now we don't need to call the controllers explicitly. As you may have identified the '/register' and '/login' are the endpoints and 'studentRegistration' and 'studentLogin' are the functions we defined. The functions are called respectively as the end points are hited.

Now we will define the protected route. To protect it we need to enclose it within laravel sanctum Middleware

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

Route::group(['middleware' => 'auth:sanctum'], function () {
      Route::controller(Authentication::class)->middleware(['abilities:Student'])->group( function () {
            Route::get('/myName','studentName');
        })
});

The first middleware that is the auth:sanctum would checl for a valid api token where as the next middleware would chek if the token is with the ability student or not. Accordingly it would let users access routes or decline if they don't belong to that role or don't have valid api token.

Finally your routes/api.php fill should look like as follows

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

Route::group(['middleware' => 'auth:sanctum'], function () {
      Route::controller(Authentication::class)->middleware(['abilities:Student'])->group( function () {
            Route::get('/myName','studentName');
        })
});

Route::controller(AuthenticationController::class)->group(function(){
    Route::post('/register','studentRegistration');
    Route::post('/login','studentLogin')
});

Task for you

  • Add admin to your application.

  • Authenticated Admin is only able to register students

  • Authenticated Admin can view all the students

How can we make it better

  • adding validation

  • exception handeling