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