Kritim Yantra
Jun 03, 2025
Controlling who can access what is essential for any web application. Whether you're building a blog, admin panel, or a SaaS platform—Role-Based Access Control (RBAC) ensures users only do what they're allowed to.
Laravel 12 makes this easy with two built-in tools:
In this guide, you’ll learn how to implement a full role and permission system using Laravel’s Gates and Policies. We’ll keep it simple, clean, and practical 💡
Before we start coding, let’s understand these tools:
Feature | Gates | Policies |
---|---|---|
Type | Closure-based | Class-based |
Best For | General checks (e.g. "access-dashboard") | Model-specific checks (e.g. "edit-post") |
✅ Use Gates: When the action is general, like accessing an admin panel
✅ Use Policies: When the action depends on a specific model, like editing a post
We’ll need tables for roles, permissions, and their relationships.
// Roles table
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('description')->nullable();
$table->timestamps();
});
// Permissions table
Schema::create('permissions', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('description')->nullable();
$table->timestamps();
});
// Pivot table to link roles and permissions
Schema::create('role_permission', function (Blueprint $table) {
$table->foreignId('role_id')->constrained()->cascadeOnDelete();
$table->foreignId('permission_id')->constrained()->cascadeOnDelete();
$table->primary(['role_id', 'permission_id']);
});
// Add role to users
Schema::table('users', function (Blueprint $table) {
$table->foreignId('role_id')->constrained()->default(3); // default = 'user'
});
// app/Models/Role.php
class Role extends Model {
public function users() {
return $this->hasMany(User::class);
}
public function permissions() {
return $this->belongsToMany(Permission::class);
}
}
// app/Models/Permission.php
class Permission extends Model {
public function roles() {
return $this->belongsToMany(Role::class);
}
}
// app/Models/User.php
class User extends Authenticatable {
public function role() {
return $this->belongsTo(Role::class);
}
public function hasPermission($name) {
return $this->role->permissions->contains('name', $name);
}
}
AuthServiceProvider
use Illuminate\Support\Facades\Gate;
public function boot(): void {
$this->registerPolicies();
// Dynamic Gates from DB
foreach (Permission::all() as $permission) {
Gate::define($permission->name, function ($user) use ($permission) {
return $user->hasPermission($permission->name);
});
}
// Or define manually
Gate::define('access-admin-dashboard', fn($user) => $user->role->name === 'admin');
}
// In controller
if (Gate::allows('edit-settings')) {
// Allow editing
}
// In Blade
@can('edit-settings')
<a href="/settings/edit">Edit Settings</a>
@endcan
php artisan make:policy PostPolicy --model=Post
// app/Policies/PostPolicy.php
class PostPolicy {
public function view(User $user, Post $post) {
return $user->hasPermission('view-posts') ||
($post->user_id == $user->id && $user->hasPermission('view-own-posts'));
}
public function create(User $user) {
return $user->hasPermission('create-posts');
}
public function update(User $user, Post $post) {
return $user->hasPermission('update-any-posts') ||
($post->user_id == $user->id && $user->hasPermission('update-own-posts'));
}
public function delete(User $user, Post $post) {
return $user->hasPermission('delete-any-posts') ||
($post->user_id == $user->id && $user->hasPermission('delete-own-posts'));
}
}
protected $policies = [
Post::class => PostPolicy::class,
];
// Controller
$this->authorize('update', $post);
// Blade
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Edit</a>
@endcan
Route::post('/post', function () {
// Create post logic
})->middleware('can:create,App\Models\Post');
@can('create', App\Models\Post::class)
<a href="{{ route('posts.create') }}">New Post</a>
@endcan
@cannot('delete', $post)
You can't delete this post.
@endcannot
// User model
public function getPermissionsAttribute() {
return Cache::remember("user.{$this->id}.permissions", now()->addDay(), function () {
return $this->role->permissions->pluck('name')->toArray();
});
}
public function hasPermission($name) {
return in_array($name, $this->permissions);
}
// tests/Feature/PostPolicyTest.php
public function test_admin_can_update_any_post() {
$admin = User::factory()->create(['role_id' => Role::where('name', 'admin')->first()->id]);
$post = Post::factory()->create();
$this->assertTrue($admin->can('update', $post));
}
public function test_user_cannot_delete_others_posts() {
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$post = Post::factory()->create(['user_id' => $user2->id]);
$this->assertFalse($user1->can('delete', $post));
}
// app/Console/Commands/CreatePermission.php
protected $signature = 'permission:create {name} {description?}';
public function handle() {
$permission = Permission::create([
'name' => $this->argument('name'),
'description' => $this->argument('description') ?? '',
]);
$this->info("Permission {$permission->name} created!");
}
edit-user
, delete-post
)Laravel makes it easy to build secure, maintainable role and permission systems using Gates and Policies. By following the steps above, you can:
🔐 Authorization isn’t just a feature—it’s a security backbone. So take time to plan, test, and document your permissions well.
Happy coding! 🚀
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google