DEV Community

Patrick Lusaya
Patrick Lusaya

Posted on • Updated on

How to Implement Role-Based Authentication in Laravel with Sanctum: A Step-by-Step Guide

Role-based authentication is a crucial aspect of web application development, especially when it comes to managing user access to different parts of the system. In Laravel, Sanctum is a popular package for implementing API authentication, including role-based access control. This guide will provide a step-by-step approach to implement role-based authentication in Laravel using Sanctum.

Our API will have users with two types of roles: admin and moderator. Admin will create moderators and Moderators will basically perform all other operations that may be needed.

Step 1: Setting up our project.

  • Create a laravel project by running the command composer create-project laravel/laravel myProjectName anywhere you want to create your project.

  • After that create your database in any type of database management system of your choice then head over to .env file of your project and configure the following
    :

DB_CONNECTION=pgsql //fill according to your database
DB_HOST=127.0.0.1
DB_PORT=5432 //fill according to your database
DB_DATABASE=databaseName
DB_USERNAME=yourUsername
DB_PASSWORD=yourPassword
Enter fullscreen mode Exit fullscreen mode
  • Now, install sanctum to your project by running the command composer require laravel/sanctum on your project's root directory.
  • Next, you should publish the Sanctum configuration and migration files using the command php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

  • After that, you should replace Sanctum's middleware of your api middleware group within your application's app/Http/Kernel.php file with:

'api' => [
    \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Enter fullscreen mode Exit fullscreen mode

Finally, run php artisan migrate

Step 2: Create users table and User Model

In your migrations folder open the migration file create_users_table and on the up function replace the existing code with:

Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('username')->unique();
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->enum('role', [ 'admin', 'moderator'])->default('admin');
            $table->timestamps();
        });
Enter fullscreen mode Exit fullscreen mode
  • What this does is it creates a users table with the columns username, email, password, role and others as described in the function.

  • $table->enum('role', [ 'admin', 'moderator'])->default('admin'); - This adds a "role" column to the "users" table with an enumerated data type and a default value of "admin". The "role" column can only contain values of "admin" or "moderator". Now, what we are doing here is that if the the registration request won't have a role in it then by default that user will have a role of admin.

Run php artisan migrate to create the users table.

Then,create a User model with fillable fields: In Laravel, you can create a User model by running the php artisan make:model User command in the terminal. Once you've done that, you can define the model's fillable fields by adding the following code to the model file:

protected $fillable = [
    'username',
    'email',
    'password',
    'role'
];
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a middleware to check roles of users.

A middleware simply sits between the server and your request, it checks to see if you're allowed to access that information or perform that action.

We will create a checkRole middleware by running the command php artisan make:middleware checkRole.
Once that is done open the middleware and paste the following code :

public function handle(Request $request, Closure $next, ...$roles)
    {

        $user = $request->user();

        if (! $user || ! in_array($user->role, $roles)) {
            abort(403, 'Unauthorized');
        }

        return $next($request);

    }
Enter fullscreen mode Exit fullscreen mode
  • What this does is it checks if the authenticated user has a role that is allowed to access a certain route. If the user does not have the required role, it aborts the request and returns a 403 (Forbidden) error message. Otherwise, it passes the request to the next middleware in the chain.

  • Now register the middleware you just created by adding it in the array of routeMiddleware in your kernel.php as 'restrictRole' =>
    \App\Http\Middleware\CheckRole::class,

Step 4: Create Authentication Controller

This controller will handle our registration and login logic.

  • But remember, since only admin can create moderators, Then the registration endpoint must be protected because we don't want other users to assign roles to themselves. Therefore we will create an initial default admin who can then create other admins and moderators.

  • To do this we need to create a seeder called defaultAdmin by running the command php artisan make:seeder defaultAdmin. Once that is done open the file and paste the following code to create a default initial user:

public function run()
    {
        User::create(
            [
            'username' => 'admin',
            'email' => 'admin@jf.com',
            'password' => bcrypt('password'),
            'role' => 'admin',
            ]
            );
    }

Enter fullscreen mode Exit fullscreen mode
  • Now you can proceed creating the Authentication controller by running php artisan make:controller AuthController

  • Open the file and paste the following code to handle registration and login
    :

public function register( Request $request){
        $data = $request->validate([
            'username' => 'required|unique:users',
            'email' => 'required|unique:users',
            'password' => 'required|confirmed',
            'role' => 'in:admin,moderator',
        ]);

        // Set default role to 'admin' if not provided in the request
        $userData = array_merge($data, ['role' => $data['role'] ?? 'admin']);


        // Mass assign the validated request data to a new instance of the User model
        $user = User::create($userData);
        $token = $user->createToken('my-token')->plainTextToken;

        return response()->json([
            'token' =>$token,
            'Type' => 'Bearer'
        ]);
    }

    public function login(Request $request)
    {
        $fields = $request->validate([
            'username' => 'required',
            'password' => 'required',
        ]);

        $user = User::where('username', $fields['username'])->first();

        if (!$user || !Hash::check($fields['password'], $user->password)) {
            return response([
                'message' => 'Wrong credentials'
            ]);
        }

        $token = $user->createToken('my-token')->plainTextToken;

        return response()->json([
            'token' => $token,
            'Type' => 'Bearer',
            'role' => $user->role // include user role in response
        ]);
    }
Enter fullscreen mode Exit fullscreen mode

Code Explanation

  • The register function first uses the validate method of the request object to validate the input data against some rules defined in an array.

  • If the validation passes, the function continues by merging the validated data with a default value for the role field, if not provided in the request.

  • The function then creates a new instance of the User model with the validated and merged data, using the create method, which performs a mass assignment of the input data to the new user object.

  • After creating the user, the function generates an access token for the user using the createToken method of the user object, and returns a JSON response containing the token and its type. The token can be used by the client to authenticate subsequent requests to protected routes on the server
    .

  • The login function first validates the input data (the username and password fields) using the validate method of the request object. The function then retrieves a user instance from the database based on the provided username. If the user doesn't exist or the password is incorrect, it returns a response with the message "Wrong credentials".

  • If the user exists and the password is correct, the function generates an access token for the user using thecreateToken method of the user object and returns a JSON response containing the token, its type, and the user's role. The token can be used by the client to authenticate subsequent requests to protected routes on the server.

Step 5: Create routes

Open api/routes.api file and paste the following code

//public route
Route::post('/auth/login', [AuthController::class, 'login']);
//protected route
Route::group(['middleware' => ['auth:sanctum']], function(){
 Route::post('/auth/register', [AuthController::class, 'register'])->middleware('restrictRole:admin');
Route::get('/users', [PostController::class, 'show'])->middleware('restrictRole:admin');
Route::put('/users/{id}', [PostController::class, 'update'])->middleware('restrictRole:moderator');
});
Enter fullscreen mode Exit fullscreen mode
  • You may notice we have added two more routes, a GETrequest to users and a PUT request to users. We want only admin to have access to the endpoint that gets users and only moderators to have access to the endpoint that updates users.

  • Now, as you have seen in the above code all these routes have been placed within a single function and under one group. A route group allows you to group several routes together and apply the same middleware, namespace, prefix, or other common attributes to them.

  • In the provided code, Route::group is a function that creates a new route group with middleware. The middleware in this case is auth:sanctum, which indicates that any route within this group requires authentication using the Sanctum middleware.

  • In the protected routes we have used the ->middleware() method which assigns middleware to a specific route in Laravel. In the provided code,we have assigned to the route our custom restrictRole middleware which we created and registered to the routeMiddleware array of the kernel.php file.

  • It checks if the authenticated user has any of the specified roles. If the user has one of the specified roles, the request continues to the route's controller or closure. If the user does not have any of the specified roles, the middleware will return a response indicating that the user is not authorized to access the route.

  • Since all this is clear now lets head over to our AuthController and add functions show and update which have been used by the last two routes.
    In your AuthController add this code:

// You could basically write some functional code here to 
// perform these operations
public function show(){
return " All users are shown here";
}

public function update(Request $request , $id){
return " All updates on users are done here";
}
Enter fullscreen mode Exit fullscreen mode
  • From here an admin could see all users and moderators could update them by their id. Since we already have one default admin, you can now login and register other users(admin/moderators) and try to access the restricted endpoints.

Here is a sample registration and login requests.

{
"username":"James",
"email": "jamie@gmail.com",
"password": "123456",
"password_confirmation": "123456",
"role" : "moderator",
}
Enter fullscreen mode Exit fullscreen mode

Login request

{
"username": "James",
"password": "123456"
}
Enter fullscreen mode Exit fullscreen mode

Here you will create a moderator and after login the user can access the routes which he has authority to.

That's all for today. Hope you got something from this one . Enjoy !!

Top comments (2)

Collapse
 
tihana profile image
Tihana

What if user is in more than 1 role?

Collapse
 
spenkau profile image
Alexander

Create pivot table user_role