Introduction
Securing your Laravel application starts with proper access control. Role-Based Access Control (RBAC) ensures users can only access features and data they're authorized to use. Whether you're building a SaaS platform, CMS, or enterprise application, RBAC is essential for maintaining security and organization.
In this comprehensive guide, we'll implement RBAC in Laravel 12 using Spatie's Laravel Permission package — the most popular and battle-tested permission management solution in the Laravel ecosystem. With over 12 million downloads, Spatie Permission simplifies role and permission management while providing powerful features out of the box.
By the end of this tutorial, you'll have a fully functional RBAC system with roles, permissions, middleware protection, and Blade directives for conditional UI rendering.
Why Use Spatie Laravel Permission?
While you could build RBAC from scratch, Spatie's package offers significant advantages:
- Zero Configuration – Works immediately after installation
- Eloquent Integration – Seamless relationships with your User model
- Multiple Guards – Support for web, API, and custom guards
- Wildcard Permissions – Check permissions using patterns
- Teams/Multi-tenancy Support – Isolate permissions per team
- Caching Layer – Optimized performance for permission checks
- Blade Directives – Clean template syntax for authorization
- Well Maintained – Regular updates and Laravel 12 compatibility
Installation and Setup
Step 1: Install the Package
Install Spatie Laravel Permission via Composer:
composer require spatie/laravel-permission
Step 2: Publish Configuration and Migrations
Publish the package configuration and migration files:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
This creates:
config/permission.php– Configuration file- Migration file for roles and permissions tables
Step 3: Run Migrations
Execute migrations to create the necessary database tables:
php artisan migrate
This creates five tables:
roles– Stores role definitionspermissions– Stores permission definitionsmodel_has_permissions– Direct user permissionsmodel_has_roles– User-role assignmentsrole_has_permissions– Role-permission assignments
Step 4: Add Trait to User Model
Update your App\Models\User model to include the HasRoles trait:
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
// Your existing code...
}
Creating Roles and Permissions
Understanding the Hierarchy
In Spatie's RBAC system:
- Permissions are specific actions (e.g.,
edit posts,delete users) - Roles are groups of permissions (e.g.,
Admin,Editor) - Users are assigned roles and inherit their permissions
Creating Permissions
Create permissions using Eloquent or the artisan tinker:
use Spatie\Permission\Models\Permission;
Permission::create(['name' => 'create posts']);
Permission::create(['name' => 'edit posts']);
Permission::create(['name' => 'delete posts']);
Permission::create(['name' => 'publish posts']);
Permission::create(['name' => 'manage users']);
Permission::create(['name' => 'manage roles']);
Permission::create(['name' => 'view analytics']);
Creating Roles
Create roles and assign permissions:
use Spatie\Permission\Models\Role;
// Create roles
$adminRole = Role::create(['name' => 'admin']);
$editorRole = Role::create(['name' => 'editor']);
$writerRole = Role::create(['name' => 'writer']);
// Assign all permissions to admin
$adminRole->givePermissionTo(Permission::all());
// Assign specific permissions to editor
$editorRole->givePermissionTo([
'create posts',
'edit posts',
'publish posts',
'view analytics'
]);
// Assign limited permissions to writer
$writerRole->givePermissionTo([
'create posts',
'edit posts'
]);
Seeding Roles and Permissions
Create a seeder for production-ready setup:
php artisan make:seeder RolePermissionSeeder
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RolePermissionSeeder extends Seeder
{
public function run(): void
{
// Reset cached roles and permissions
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// Create permissions
$permissions = [
'create posts',
'edit posts',
'delete posts',
'publish posts',
'manage users',
'manage roles',
'view analytics',
'manage settings',
];
foreach ($permissions as $permission) {
Permission::create(['name' => $permission]);
}
// Create Super Admin role
$superAdmin = Role::create(['name' => 'super-admin']);
$superAdmin->givePermissionTo(Permission::all());
// Create Admin role
$admin = Role::create(['name' => 'admin']);
$admin->givePermissionTo([
'create posts',
'edit posts',
'delete posts',
'publish posts',
'view analytics',
]);
// Create Editor role
$editor = Role::create(['name' => 'editor']);
$editor->givePermissionTo([
'create posts',
'edit posts',
'publish posts',
]);
// Create Writer role
$writer = Role::create(['name' => 'writer']);
$writer->givePermissionTo([
'create posts',
'edit posts',
]);
}
}
Run the seeder:
php artisan db:seed --class=RolePermissionSeeder
Assigning Roles to Users
Assign Roles
Assign single or multiple roles to users:
use App\Models\User;
$user = User::find(1);
// Assign a single role
$user->assignRole('admin');
// Assign multiple roles
$user->assignRole(['editor', 'writer']);
// Assign role using Role model
$user->assignRole(Role::findByName('admin'));
Remove Roles
// Remove a specific role
$user->removeRole('editor');
// Remove all roles
$user->syncRoles([]);
Sync Roles
Replace all existing roles with new ones:
// User will only have 'admin' role
$user->syncRoles(['admin']);
Direct Permission Assignment
Assign permissions directly to users (bypassing roles):
$user->givePermissionTo('manage settings');
// Give multiple permissions
$user->givePermissionTo(['create posts', 'edit posts']);
// Revoke permission
$user->revokePermissionTo('delete posts');
Checking Permissions and Roles
Check User Roles
// Check single role
if ($user->hasRole('admin')) {
// User is an admin
}
// Check any of multiple roles
if ($user->hasAnyRole(['admin', 'editor'])) {
// User is either admin or editor
}
// Check all roles
if ($user->hasAllRoles(['admin', 'super-admin'])) {
// User has both roles
}
Check Permissions
// Check single permission
if ($user->hasPermissionTo('edit posts')) {
// User can edit posts
}
// Alternative syntax
if ($user->can('edit posts')) {
// User can edit posts
}
// Check multiple permissions (any)
if ($user->hasAnyPermission(['edit posts', 'delete posts'])) {
// User has at least one permission
}
// Check multiple permissions (all)
if ($user->hasAllPermissions(['create posts', 'edit posts'])) {
// User has both permissions
}
Protecting Routes with Middleware
Spatie Permission provides built-in middleware for route protection.
Register Middleware
In Laravel 12, register middleware in bootstrap/app.php:
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
Protect Routes by Role
// Single role
Route::middleware(['auth', 'role:admin'])->group(function () {
Route::get('/admin/dashboard', [AdminController::class, 'index']);
});
// Multiple roles (OR condition)
Route::middleware(['auth', 'role:admin|editor'])->group(function () {
Route::get('/posts', [PostController::class, 'index']);
});
Protect Routes by Permission
// Single permission
Route::middleware(['auth', 'permission:edit posts'])->group(function () {
Route::put('/posts/{post}', [PostController::class, 'update']);
});
// Multiple permissions (OR condition)
Route::middleware(['auth', 'permission:create posts|edit posts'])->group(function () {
Route::resource('posts', PostController::class);
});
Combined Role and Permission Check
Route::middleware(['auth', 'role_or_permission:admin|edit posts'])->group(function () {
Route::get('/posts/edit', [PostController::class, 'edit']);
});
Controller Authorization
Using authorize() Method
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function edit(Post $post)
{
$this->authorize('edit posts');
return view('posts.edit', compact('post'));
}
public function destroy(Post $post)
{
$this->authorize('delete posts');
$post->delete();
return redirect()->route('posts.index')
->with('success', 'Post deleted successfully');
}
}
Using Gate Facade
use Illuminate\Support\Facades\Gate;
public function update(Request $request, Post $post)
{
if (Gate::denies('edit posts')) {
abort(403, 'Unauthorized action');
}
$post->update($request->validated());
return redirect()->route('posts.show', $post);
}
Blade Directives for Conditional UI
Role-Based Directives
@role('admin')
<a href="{{ route('admin.dashboard') }}">Admin Dashboard</a>
@endrole
@hasrole('editor')
<button>Edit Content</button>
@endhasrole
@hasanyrole('admin|editor')
<div>Admin or Editor Content</div>
@endhasanyrole
Permission-Based Directives
@can('create posts')
<a href="{{ route('posts.create') }}" class="btn btn-primary">
Create New Post
</a>
@endcan
@can('delete posts')
<form action="{{ route('posts.destroy', $post) }}" method="POST">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">Delete</button>
</form>
@endcan
@canany(['edit posts', 'delete posts'])
<div>Post Management Tools</div>
@endcanany
Combined Checks
@role('admin')
@can('manage users')
<a href="{{ route('users.index') }}">Manage Users</a>
@endcan
@endrole
Advanced Features
Super Admin Override
Grant all permissions to super admins automatically in App\Providers\AuthServiceProvider:
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::before(function ($user, $ability) {
return $user->hasRole('super-admin') ? true : null;
});
}
Wildcard Permissions
Check permissions using wildcards:
$user->givePermissionTo('posts.*');
// Check wildcard
if ($user->can('posts.*')) {
// User can do anything with posts
}
Cache Management
Clear permission cache after updates:
use Spatie\Permission\PermissionRegistrar;
app(PermissionRegistrar::class)->forgetCachedPermissions();
// Or using artisan
php artisan permission:cache-reset
Conclusion
Implementing RBAC in Laravel 12 with Spatie's Laravel Permission package provides enterprise-grade access control with minimal code. The package handles complex permission logic, caching, and guard management automatically, letting you focus on building features.
Key takeaways:
- Install and configure Spatie Permission in minutes
- Create granular permissions and flexible roles
- Protect routes with dedicated middleware
- Build conditional UIs using Blade directives
- Scale permissions across teams and multi-tenant applications
Need help implementing RBAC in your Laravel project? NeedLaravelSite specializes in building secure, scalable Laravel applications with proper access control systems. Contact us for expert Laravel development services.
Related Resources: