This in-depth guide explores Laravel’s Policies and Gates, giving you the step-by-step skills to implement role-based access control (RBAC) in your applications. We’ll dive into practical coding examples, explain best practices, and show how to keep your Laravel applications secure. This tutorial is written like a professional teaching script—ideal for reading or recording as a video walkthrough.
Authorization is one of the most critical aspects of any web application. Laravel provides two main approaches for handling authorization: Gates and Policies. While both serve the same purpose—defining who can do what—each shines in different scenarios.
In this tutorial, we’ll cover:
What Gates and Policies are
How to implement simple role checks
When to use a Gate vs. a Policy
Building role-based access control step by step
Practical examples you can adapt to your projects
Understanding Authorization in Laravel
Laravel separates authentication (who the user is) from authorization (what the user can do). Once a user logs in, you need to control access to resources based on roles or permissions. That’s where Gates and Policies come in.
Gates vs. Policies
Here’s a simple comparison:
Feature
Gates
Policies
Definition
Closures that determine if a user is authorized
Classes dedicated to authorization logic for a model
Use case
Simple, one-off checks
Complex, model-related permissions
Organization
Defined in AuthServiceProvider
Separate class files inside app/Policies
Setting Up Roles and Permissions
Before we dive deep into Gates and Policies, let’s define a simple role system. For this tutorial, assume each user has a column role in the database.
// Migration snippet for users table
$table->string('role')->default('user');
Common roles can be:
admin - Full access
editor - Limited access to content
user - Regular access
Working with Gates
Let’s start with Gates, the simplest way to define authorization rules.
Step 1: Defining a Gate
// app/Providers/AuthServiceProvider.php
use Illuminate\Support\Facades\Gate;
public function boot()
{
Gate::define('edit-posts', function ($user) {
return $user->role === 'editor' || $user->role === 'admin';
});
}
Step 2: Using a Gate in Controllers
public function update(Post $post)
{
if (Gate::denies('edit-posts')) {
abort(403, 'You are not allowed to edit posts');
}
// Update logic here
}
When authorization rules become more complex, it’s better to use Policies.
Step 1: Generate a Policy
php artisan make:policy PostPolicy --model=Post
Step 2: Define Policy Methods
// app/Policies/PostPolicy.php
public function update(User $user, Post $post)
{
return $user->id === $post->user_id || $user->role === 'admin';
}
public function delete(User $user, Post $post)
{
return $user->role === 'admin';
}
Route::get('/admin', function () {
return 'Welcome Admin';
})->middleware('role:admin');
Where Gates and Policies Fit Together
You don’t have to choose one exclusively. In real applications:
Use Gates for quick checks.
Use Policies for model-level authorization.
Use Middleware for route-level role checks.
Best Practices
Keep authorization logic out of controllers.
Use policies for anything tied to a model.
Organize roles and permissions in a database for flexibility.
Combine Gates and Policies strategically.
Full Practical Example: Laravel Policies & Gates (RBAC)
Prerequisites
Laravel 10 or 11 project installed and database configured in .env
Authentication scaffold (e.g., Breeze/Jetstream) recommended so you can log in. If you don’t have one, you can still use tinker to log in a seeded user.
# optional but recommended for quick login/register
composer require laravel/breeze --dev
php artisan breeze:install
php artisan migrate
npm install && npm run dev
1) Database Migrations
1.1 Add a role column to users
Create a migration to add a role to the users table.
Open the generated file in database/migrations/*_add_role_to_users_table.php and paste:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('role')->default('user')->after('email');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->text('body');
$table->boolean('is_published')->default(false);
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Run migrations:
php artisan migrate
2) Models
2.1 Post model
php artisan make:model Post
Edit app/Models/Post.php :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ['title', 'body', 'is_published'];
public function user()
{
return $this->belongsTo(User::class);
}
public function scopePublished($query)
{
return $query->where('is_published', true);
}
}
2.2 Update User model (optional helpers)
In app/Models/User.php add a simple role helper and relationship:
public function posts()
{
return $this->hasMany(\App\Models\Post::class);
}
public function isRole(string $role): bool
{
return $this->role === $role;
}
3) Factories & Seeders
3.1 PostFactory
php artisan make:factory PostFactory --model=Post
Edit database/factories/PostFactory.php :
<?php
namespace Database\Factories;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
protected $model = Post::class;
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => $this->faker->sentence(6),
'body' => $this->faker->paragraphs(4, true),
'is_published' => $this->faker->boolean(70),
];
}
}
3.2 UserFactory (ensure a default role)
Open database/factories/UserFactory.php and ensure it sets a role:
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => bcrypt('password'), // demo only
'role' => 'user',
'remember_token' => \Str::random(10),
];
}
<?php
namespace Database\Seeders;
use App\Models\Post;
use App\Models\User;
use Illuminate\Database\Seeder;
class PostSeeder extends Seeder
{
public function run(): void
{
// Some posts for admin & editor
$admin = User::where('email', 'admin@example.com')->first();
$editor = User::where('email', 'editor@example.com')->first();
Post::factory(5)->create(['user_id' => $admin->id]);
Post::factory(5)->create(['user_id' => $editor->id]);
// And posts for random users too
User::where('role', 'user')->get()->each(function ($user) {
Post::factory(2)->create(['user_id' => $user->id]);
});
}
}
3.5 DatabaseSeeder
Wire seeders in database/seeders/DatabaseSeeder.php :
public function run(): void
{
$this->call([
UserSeeder::class,
PostSeeder::class,
]);
}
Seed your database:
php artisan db:seed
4) Gates
Define a few app-wide checks in app/Providers/AuthServiceProvider.php :
<?php
namespace App\Providers;
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
// We'll map Post :: PostPolicy below in the Policies section
];
public function boot(): void
{
// Generic admin gate
Gate::define('access-admin', function (User $user) {
return $user->role === 'admin';
});
// Editors and admins can manage posts
Gate::define('manage-posts', function (User $user) {
return in_array($user->role, ['admin', 'editor']);
});
// View unpublished posts (admin or the owner)
Gate::define('view-unpublished', function (User $user, Post $post) {
return $user->role === 'admin' || $user->id === $post->user_id;
});
}
}
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
public function viewAny(User $user): bool
{
return true; // all authenticated users can view list
}
public function view(User $user, Post $post): bool
{
// published posts are public; drafts only for owner/admin/editor
return $post->is_published
|| $user->role === 'admin'
|| $user->role === 'editor'
|| $user->id === $post->user_id;
}
public function create(User $user): bool
{
// only admin/editor create posts
return in_array($user->role, ['admin', 'editor']);
}
public function update(User $user, Post $post): bool
{
// owner can edit; admin/editor can edit any
return $user->id === $post->user_id || in_array($user->role, ['admin', 'editor']);
}
public function delete(User $user, Post $post): bool
{
// only admin or owner
return $user->id === $post->user_id || $user->role === 'admin';
}
public function restore(User $user, Post $post): bool
{
return $user->role === 'admin';
}
public function forceDelete(User $user, Post $post): bool
{
return $user->role === 'admin';
}
}
Register the Policy
Update app/Providers/AuthServiceProvider.php to map the policy:
use App\Models\Post;
use App\Policies\PostPolicy;
protected $policies = [
Post::class => PostPolicy::class,
];
6) Role Middleware (Optional but Helpful)
Create a lightweight middleware to guard entire routes by role.
php artisan make:middleware RoleMiddleware
Edit app/Http/Middleware/RoleMiddleware.php :
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class RoleMiddleware
{
public function handle(Request $request, Closure $next, ...$roles)
{
$user = $request->user();
if (! $user || ! in_array($user->role, $roles)) {
abort(403, 'Unauthorized.');
}
return $next($request);
}
}
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function __construct()
{
// Auto-authorize resource methods via PostPolicy
$this->authorizeResource(Post::class, 'post');
}
public function index()
{
// everyone logged in can view list; show published + own drafts
$posts = Post::query()
->with('user')
->when(auth()->user()->role !== 'admin' && auth()->user()->role !== 'editor', function ($q) {
$q->where(function ($q) {
$q->where('is_published', true)
->orWhere('user_id', auth()->id());
});
})
->latest()
->paginate(10);
return view('posts.index', compact('posts'));
}
public function create()
{
return view('posts.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string'],
'is_published' => ['nullable', 'boolean'],
]);
$post = auth()->user()->posts()->create([
'title' => $validated['title'],
'body' => $validated['body'],
'is_published' => (bool) ($validated['is_published'] ?? false),
]);
return redirect()->route('posts.show', $post)->with('status', 'Post created!');
}
public function show(Post $post)
{
// extra check: prevent non-authorized from viewing unpublished
if (! $post->is_published && auth()->user()->cannot('view', $post)) {
abort(403);
}
return view('posts.show', compact('post'));
}
public function edit(Post $post)
{
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string'],
'is_published' => ['nullable', 'boolean'],
]);
$post->update([
'title' => $validated['title'],
'body' => $validated['body'],
'is_published' => (bool) ($validated['is_published'] ?? false),
]);
return redirect()->route('posts.show', $post)->with('status', 'Post updated!');
}
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')->with('status', 'Post deleted.');
}
}
8) Routes
Wire the routes in routes/web.php :
<?php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return redirect()->route('posts.index');
});
Route::middleware(['auth'])->group(function () {
// Example admin-only screen guarded by role middleware + gate
Route::get('/admin', function () {
abort_unless(\Gate::allows('access-admin'), 403);
return view('admin.dashboard'); // create file if you want
})->middleware('role:admin')->name('admin.dashboard');
Route::resource('posts', PostController::class);
});
9) Blade Views
Minimal Tailwind-free HTML to keep things portable. Add your CSS framework as needed.
That’s it! You now have a working RBAC demo featuring roles, Gates, Policies, and a posts module with full CRUD, controllers, routes, and Blade views. Drop this into a fresh Laravel project, run the migrations and seeders, and start experimenting.
Conclusion
By now, you should be confident using Laravel’s authorization tools. Gates are perfect for small checks, while Policies organize your model-related permissions. Together, they form a flexible RBAC system that keeps your app secure and maintainable.
This article is a comprehensive tutorial on using the Prohibited Validation Rules in Laravel. You will learn how to apply prohibited, prohibited_if, and prohibited_unless with clear explanations, real-life scenarios, and code examples. This guide is perfect for developers who want to master advanced validation techniques in Laravel applications.
This detailed tutorial explores request validation in Laravel controllers. You’ll learn multiple techniques—basic controller validation, using form request classes, custom rules, conditional validation, error handling, localization, and best practices. With practical examples, code snippets, and structured explanations, this article is designed for beginners to advance learner.
This guide teaches you how to deploy Laravel applications to production servers. From preparing your environment and configuring Nginx or Apache, to database migrations, caching, performance optimization, CI/CD pipelines, and security practices—this article covers everything step by step.It’s suitable for both beginners and advanced developers who want to ship stable, secure & scalable app.
This website uses cookies to enhance your browsing experience. By continuing to use this site, you consent to the use of cookies. Please review our Privacy Policy for more information on how we handle your data. Cookie Policy
These cookies are essential for the website to function properly.
These cookies help us understand how visitors interact with the website.
These cookies are used to deliver personalized advertisements.