Role-Based Access Control (RBAC) is a widely-used approach to managing user permissions in web applications. It allows you to define roles for different types of users and grant permissions based on those roles, enabling a scalable and flexible permission management system.
In this two-part series, we’ll explore how to implement advanced RBAC in Laravel. We’ll cover the following steps:
- Setting Up Authentication in Laravel
- Creating Role and Permission Models
- Setting Up Relationships in the User Model
- Managing Role and Permission Assignments
- Implementing Middleware for Role-based Authorization
In this first part, we’ll cover the initial setup, including authentication, creating models, setting relationships, and assigning roles and permissions.
1. Setting Up Authentication in Laravel
Before we dive into RBAC, you need a basic authentication system. Laravel provides several packages that make setting up authentication a breeze, such as Laravel Breeze, Jetstream, or Laravel UI. In this example, we’ll use Laravel Breeze.
Start by installing a new Laravel project and setting up Breeze for authentication.
Step 1: Install Laravel and Breeze
composer create-project laravel/laravel rbac-demo
cd rbac-demo
composer require laravel/breeze --dev
php artisan breeze:install
npm install && npm run dev
php artisan migrate
This will scaffold all the authentication views, routes, and controllers for registration, login, and password reset functionality.
Step 2: Verify Authentication
After running php artisan migrate
, you can visit the /login
or /register
routes in your browser to verify that authentication is working correctly. Once this is in place, we can move on to setting up roles and permissions.
2. Creating Role and Permission Models
RBAC is centered around two key concepts:
- Roles: These represent groups of permissions (e.g., Admin, Editor, User).
- Permissions: These represent specific actions users can perform (e.g., view posts, edit posts).
To implement this, we’ll create two models: Role
and Permission
.
Step 1: Generate Models and Migrations
Create models and migrations for Role
and Permission
:
php artisan make:model Role -m
php artisan make:model Permission -m
This command will create both the model files and migration files.
Step 2: Define Role and Permission Schema
Next, define the schema for the roles and permissions tables in the migrations:
database/migrations/YYYY_MM_DD_create_roles_table.php
:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateRolesTable extends Migration
{
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('roles');
}
}
database/migrations/YYYY_MM_DD_create_permissions_table.php
:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePermissionsTable extends Migration
{
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('permissions');
}
}
Step 3: Define Pivot Tables for Role-Permission and Role-User Relationships
To manage the many-to-many relationships between roles, users, and permissions, we need pivot tables.
Create migrations for these tables:
php artisan make:migration create_role_user_table
php artisan make:migration create_permission_role_table
Update the migration files to define the schema for the pivot tables:
database/migrations/YYYY_MM_DD_create_role_user_table.php
:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateRoleUserTable extends Migration
{
public function up()
{
Schema::create('role_user', function (Blueprint $table) {
$table->id();
$table->foreignId('role_id')->constrained()->onDelete('cascade');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('role_user');
}
}
database/migrations/YYYY_MM_DD_create_permission_role_table.php
:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePermissionRoleTable extends Migration
{
public function up()
{
Schema::create('permission_role', function (Blueprint $table) {
$table->id();
$table->foreignId('permission_id')->constrained()->onDelete('cascade');
$table->foreignId('role_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('permission_role');
}
}
Run the migrations:
php artisan migrate
3. Setting Up Relationships in the User Model
Now that we have the roles
, permissions
, and pivot tables in place, we need to define the relationships in our models. Let’s start by updating the Role
, Permission
, and User
models.
Step 1: Update the Role Model
Open the Role.php
model and define the relationships with User
and Permission
:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
protected $fillable = ['name'];
// A role may belong to many users
public function users()
{
return $this->belongsToMany(User::class);
}
// A role may have many permissions
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
}
Step 2: Update the Permission Model
Next, define the relationship in the Permission.php
model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Permission extends Model
{
protected $fillable = ['name'];
// A permission may belong to many roles
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
Step 3: Update the User Model
Finally, in the User.php
model, define the many-to-many relationship with Role
:
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
// A user can have multiple roles
public function roles()
{
return $this->belongsToMany(Role::class);
}
// Check if the user has a specific role
public function hasRole($role)
{
return $this->roles()->where('name', $role)->exists();
}
// Check if the user has a specific permission through their roles
public function hasPermission($permission)
{
return $this->roles()
->whereHas('permissions', function ($query) use ($permission) {
$query->where('name', $permission);
})
->exists();
}
}
4. Managing Role and Permission Assignments
Now that the relationships are set up, the next step is to manage role and permission assignments.
Step 1: Create Seeder for Roles and Permissions
Create a seeder to assign roles and permissions:
php artisan make:seeder RolesAndPermissionsSeeder
In RolesAndPermissionsSeeder.php
, assign the roles and permissions:
use App\Models\Role;
use App\Models\Permission;
use Illuminate\Database\Seeder;
class RolesAndPermissionsSeeder extends Seeder
{
public function run()
{
$adminRole = Role::create(['name' => 'Admin']);
$editorRole = Role::create(['name' => 'Editor']);
$userRole = Role::create(['name' => 'User']);
$manageUsers = Permission::create(['name' => 'manage users']);
$editPosts = Permission::create(['name' => 'edit posts']);
$viewPosts = Permission::create(['name' => 'view posts']);
// Attach permissions to roles
$adminRole->permissions()->attach([$manageUsers->id, $editPosts->id, $viewPosts->id]);
$editorRole->permissions()->attach([$editPosts->id, $viewPosts->id]);
$userRole->permissions()->attach($viewPosts->id);
}
}
Run the seeder:
php artisan db:seed --class=RolesAndPermissionsSeeder
Now, we have defined roles and permissions in the system and attached them to each other.
5. Implementing Middleware for Role-Based Authorization
The next step is to create middleware to restrict access based on user roles. Middleware is a powerful tool in Laravel to filter HTTP requests entering your application.
Step 1: Create Role Middleware
Create a new middleware class for role-based authorization:
php artisan make:middleware RoleMiddleware
In RoleMiddleware.php
, add the following logic:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RoleMiddleware
{
public function handle($request, Closure $next, $role)
{
if (!Auth::check() || !Auth::user()->hasRole($role)) {
abort(403, 'Unauthorized');
}
return $next($request);
}
}
This middleware checks if the authenticated user has the specified role. If they don’t, it aborts the request with a 403 error.
Step 2: Register the Middleware
In app/Http/Kernel.php
, register the middleware in the $routeMiddleware
array:
protected $routeMiddleware = [
// other middleware
'role' => \App\Http\Middleware\RoleMiddleware::class,
];
Step 3: Apply Middleware to Routes
You can now apply the middleware to any route or group of routes. For example:
Route::group(['middleware' => ['role:Admin']], function () {
Route::get('/admin', [AdminController::class, 'index']);
});
In this example, only users with the “Admin” role can access the /admin
route.
Conclusion of Part 1
In this first part of the tutorial, we covered the foundation for implementing advanced Role-Based Access Control in Laravel. You’ve learned how to set up authentication, create models for roles and permissions, define relationships, and restrict access using middleware.
In Part 2, we’ll dive into more advanced features like using Laravel’s policies for fine-grained control, Blade directives for role and permission checks in views, and additional tips for managing complex RBAC systems efficiently.
Comments