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
- Users Table
This table will include all information of user.
phpSchema::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' });
- Roles Table
In this table, we define the different roles available in the application (such as Admin, Editor, and Subscriber).
phpSchema::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(); });
- 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
).
phpSchema::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(); });
- 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.
phpSchema::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(); });
- 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.
phpSchema::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.
phpuse 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.
phpuse 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);