Kritim Yantra
May 25, 2025
Building modern web applications often requires controlling user access to specific features. Whether it's an admin-only dashboard or content restricted by roles, Role-Based Access Control (RBAC) is the go-to solution. In this comprehensive guide, we’ll walk you through setting up Roles and Permissions in Laravel 12, covering:
✅ Database design
✅ Model relationships
✅ Middleware for access control
✅ Blade directives for views
✅ Seeding roles and permissions
Let's get started!
Role-Based Access Control (RBAC) is a system where you assign permissions to roles, and then roles to users. For example:
This setup ensures a clean, maintainable system for controlling access across your Laravel application.
We'll need the following tables:
users
table (default in Laravel)Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
roles
tableSchema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('description')->nullable();
$table->timestamps();
});
permissions
tableSchema::create('permissions', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('description')->nullable();
$table->timestamps();
});
role_user
(many-to-many: users ↔️ roles)Schema::create('role_user', function (Blueprint $table) {
$table->foreignId('role_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->primary(['role_id', 'user_id']);
});
permission_role
(many-to-many: roles ↔️ permissions)Schema::create('permission_role', function (Blueprint $table) {
$table->foreignId('permission_id')->constrained()->cascadeOnDelete();
$table->foreignId('role_id')->constrained()->cascadeOnDelete();
$table->primary(['permission_id', 'role_id']);
});
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function assignRole($role)
{
if (is_string($role)) {
$role = Role::whereName($role)->firstOrFail();
}
return $this->roles()->syncWithoutDetaching($role);
}
public function hasRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
return !! $role->intersect($this->roles)->count();
}
public function permissions()
{
return $this->roles->map->permissions->flatten()->pluck('name')->unique();
}
public function hasPermission($permission)
{
return $this->permissions()->contains($permission);
}
public function users()
{
return $this->belongsToMany(User::class);
}
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
public function givePermissionTo($permission)
{
if (is_string($permission)) {
$permission = Permission::whereName($permission)->firstOrFail();
}
return $this->permissions()->syncWithoutDetaching($permission);
}
public function roles()
{
return $this->belongsToMany(Role::class);
}
$user = User::find(1);
$user->assignRole('admin');
$adminRole = Role::where('name', 'admin')->first();
$adminRole->givePermissionTo('edit-users');
if ($user->hasRole('admin')) {
// Admin access granted
}
if ($user->hasPermission('edit-users')) {
// Permission granted
}
Create:
php artisan make:middleware RoleMiddleware
Implement:
public function handle(Request $request, Closure $next, $role)
{
if (!auth()->user()->hasRole($role)) {
abort(403, 'Unauthorized action.');
}
return $next($request);
}
Register in Kernel.php
:
'role' => \App\Http\Middleware\RoleMiddleware::class,
Usage:
Route::get('/admin', fn() => 'Admin Panel')->middleware(['auth', 'role:admin']);
Similar steps apply:
php artisan make:middleware PermissionMiddleware
Implement:
public function handle(Request $request, Closure $next, $permission)
{
if (!auth()->user()->hasPermission($permission)) {
abort(403, 'Unauthorized action.');
}
return $next($request);
}
Add to AppServiceProvider
:
Blade::if('role', fn($role) => auth()->check() && auth()->user()->hasRole($role));
Blade::if('permission', fn($permission) => auth()->check() && auth()->user()->hasPermission($permission));
Usage:
@role('admin')
<p>Welcome, Admin!</p>
@endrole
@permission('edit-users')
<button>Edit User</button>
@endpermission
Create a seeder:
php artisan make:seeder RolesAndPermissionsSeeder
Example:
public function run()
{
$admin = Role::create(['name' => 'admin']);
$editor = Role::create(['name' => 'editor']);
$permissions = ['manage-users', 'edit-content', 'publish-content'];
foreach ($permissions as $perm) {
Permission::create(['name' => $perm]);
}
$admin->givePermissionTo(Permission::all());
$editor->givePermissionTo(['edit-content', 'publish-content']);
}
Run:
php artisan db:seed --class=RolesAndPermissionsSeeder
✅ Use Enums for role/permission names.
✅ Cache permissions for performance.
✅ Build an admin UI for roles & permissions.
✅ Add audit logs for changes.
✅ Write tests for authorization logic.
By following this guide, you now have a powerful, flexible, and scalable Roles & Permissions system in Laravel 12! 🚀
✅ Database relationships? ✔️
✅ Model methods? ✔️
✅ Middleware & Blade directives? ✔️
✅ Seeding data? ✔️
Keep building and securing your Laravel apps with confidence! 💪
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google