NOTE / 09 JUN, 2025

Laravel RBAC (Role-Based Access Control) System

Role Based Access Control (RBAC) is a strong and simple way to manage what a user can do in the system. Instead of giving permissions to each user one by one, we will asign the permissions to a role (like "admin" or "editor").

This guide will show you how to build a good RBAC system in your Laravel app.

Designing the Database Schema

  1. Users Table

This table will include all information of user.

php
Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('first_name'); $table->string('last_name'); $table->string('username')->unique(); $table->string('email')->unique(); $table->string('password'); // Add any other user-specific fields here, like 'is_active', 'profile_image', etc. $table->timestamps(); // For 'created_at' and 'updated_at' });
  1. Roles Table

In this table, we define the different roles available in the application (such as Admin, Editor, and Subscriber).

php
Schema::create('roles', function (Blueprint $table) { $table->id(); $table->string('name')->unique(); // Unique name for the role (e.g., 'admin', 'editor') $table->enum('guard_name', ['web', 'api'])->default('web'); // For Laravel's authentication guards $table->timestamps(); });
  1. Permissions Table

This table is going to show all the different things that a user might be able to do or not (for example, create-post, edit-user, delete-comment).

php
Schema::create('permissions', function (Blueprint $table) { $table->id(); $table->string('action_name')->unique(); // Unique name for the permission (e.g., 'view-dashboard') $table->text('description')->nullable(); // A helpful explanation of what the permission does $table->string('controller')->nullable(); // Optional: to link to specific controller actions $table->timestamps(); });
  1. Role User Pivot Table (role_user):

This table handle the m2m relationship between users and roles. A single user can have many roles, while any role can be assigned to multiple users.

php
Schema::create('role_user', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); // When a user is deleted, their role assignments are too $table->foreignId('role_id')->constrained()->cascadeOnDelete(); // When a role is deleted, its user assignments are too $table->timestamps(); });
  1. Role Permissions Pivot Table (role_permissions):

In the same way, this table manages the m2m relationship between roles and permissions. A role can have many permissions, and a permission can belong to many roles.

php
Schema::create('role_permissions', function (Blueprint $table) { $table->id(); $table->foreignId('role_id')->constrained()->cascadeOnDelete(); // If a role is deleted, its permissions links are removed $table->foreignId('permission_id')->constrained()->cascadeOnDelete(); // If a permission is deleted, its role links are removed $table->timestamps(); });

Implementing Your Models

The User Model

Extend your existing User model by adding a relationship with Role and implementing an easy method to verify permissions.

php
// app/Models/User.php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { use Notifiable; // ... (your existing fillable, hidden, casts properties) /** * A user can have many roles. */ public function roles() { return $this->belongsToMany(Role::class, 'role_user'); } /** * Check if the user has a given permission(s). * * @param string|array $permission The permission action name(s) to check. * @return bool */ public function hasPermission($permission): bool { // Convert single permission string to an array for consistent handling $permissionsToCheck = is_array($permission) ? $permission : [$permission]; // Loop through each role the user has foreach ($this->roles as $role) { // Check if any of the role's permissions match the ones we're looking for if ($role->permissions()->whereIn('action_name', $permissionsToCheck)->exists()) { return true; // Found a match, user has the permission } } return false; // No matching permission found across any of the user's roles } }

The Role Model

The Role model will define its relationship with both the User and Permission models.

php
// app/Models/Role.php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Role extends Model { protected $fillable = ['name', 'guard_name']; /** * A role can be assigned to many users. */ public function users() { return $this->belongsToMany(User::class, 'role_user'); } /** * A role can have many permissions. */ public function permissions() { return $this->belongsToMany(Permission::class, 'role_permissions'); } }

The Permission Model

The Permission model just outlines how it connects to the Role.

php
// app/Models/Permission.php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Permission extends Model { protected $fillable = ['action_name', 'description']; /** * A permission can belong to many roles. */ public function roles() { return $this->belongsToMany(Role::class, 'role_permissions'); } }

Putting RBAC into Practice: Usage Examples

Now that your models and relationships are set up, let's see how you can interact with your RBAC system in your application logic.

Checking User Permissions

This is probably the most common operation. You can verify whether a logged-in user has the necessary permissions before letting them take an action or access certain content.

php
// In your Controller, Middleware, or Blade View: // Example 1: Check for a single permission if (auth()->user()->hasPermission('create-post')) { // Show the 'Create New Post' button or allow post creation return view('posts.create'); } else { abort(403, 'Unauthorized action.'); // Or redirect } // Example 2: Check for multiple permissions (user needs at least one of them) if (auth()->user()->hasPermission(['edit-post', 'delete-post'])) { // Allow editing or deleting of posts // This could mean showing edit/delete buttons }

Assigning and Removing Roles from Users

It's easy to manage user roles using the roles() relationship.

php
use App\Models\User; use App\Models\Role; // Assuming you have a user and role ID $user = User::find(1); $adminRole = Role::where('name', 'admin')->first(); $editorRole = Role::where('name', 'editor')->first(); // Assign a single role to a user if ($adminRole) { $user->roles()->attach($adminRole->id); // Adds the 'admin' role } // Remove a role from a user if ($adminRole) { $user->roles()->detach($adminRole->id); // Removes the 'admin' role } // Sync multiple roles for a user (attaches only the ones in the array, detaches others) $newRoles = [ $editorRole->id, // Add other role IDs the user should have ]; $user->roles()->sync($newRoles);

Managing Permissions for Roles

It's just as simple to define what permissions a role has.

php
use App\Models\Role; use App\Models\Permission; // Assuming you have a role and permission IDs $editorRole = Role::where('name', 'editor')->first(); $createPostPermission = Permission::where('action_name', 'create-post')->first(); $editPostPermission = Permission::where('action_name', 'edit-post')->first(); if ($editorRole && $createPostPermission) { // Assign a permission to a role $editorRole->permissions()->attach($createPostPermission->id); } // Remove a permission from a role if ($editorRole && $editPostPermission) { $editorRole->permissions()->detach($editPostPermission->id); } // Sync multiple permissions for a role $editorPermissions = [ $createPostPermission->id, $editPostPermission->id, // ...other permission IDs for the editor ]; $editorRole->permissions()->sync($editorPermissions);
SHARE: