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! 🚀
Transform from beginner to Laravel expert with our personalized Coaching Class starting June 20, 2025. Limited enrollment ensures focused attention.
1-hour personalized coaching
Build portfolio applications
Industry-standard techniques
Interview prep & job guidance
Complete your application to secure your spot
Thank you for your interest in our Laravel mentorship program. We'll contact you within 24 hours with next steps.
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google