Learn Laravel Policies & Gates | Step-by-Step Guide

Learn Laravel Policies & Gates | Step-by-Step Guide

In this tutorial, you’ll learn how to use Laravel Policies and Gates to control access to specific actions (like edit, delete, and view) in your application.

We’ll continue from the Laravel 12 CRUD app with authentication you built earlier.

What Are Policies and Gates?

  • Gates: Define simple authorization logic using closures.

  • Policies: Group authorization logic for a specific model (like Post).

Example:

  • Only the post’s author can edit or delete their post.

  • All users can view posts.

Step 1 — Prerequisites

Make sure you have:

  • A working Laravel 12 CRUD app (from the previous tutorial)

  • Authentication enabled (php artisan ui bootstrap --auth)

  • A posts table with a user_id column (to track the post’s author)

Step 2 — Add user_id to the posts table

We’ll update the A posts table to store which user created each post.

Run migration command:

php artisan make:migration add_user_id_to_posts_table --table=posts

Edit the new migration file in database/migrations/:

public function up(): void { Schema::table('posts', function (Blueprint $table) { $table->foreignId('user_id')->constrained()->onDelete('cascade'); }); }

Then migrate:

php artisan migrate

Step 3 — Update Post model

Open app/Models/Post.php and add the relationship to User:

public function user() { return $this->belongsTo(User::class); }

Also ensure $fillable includes user_id:

protected $fillable = ['title', 'content', 'user_id'];

Step 4 — Update the Controller to store the current user

In app/Http/Controllers/PostController.php, update your store() method:

public function store(Request $request) { $request->validate([ 'title' => 'required|max:255', 'content' => 'required', ]); $request->user()->posts()->create($request->only('title', 'content')); return redirect()->route('posts.index') ->with('success', 'Post created successfully.'); }

Make sure your User model (app/Models/User.php) has this relationship:

public function posts() { return $this->hasMany(Post::class); }

Step 5 — Create a Policy for Post

Run this command:

php artisan make:policy PostPolicy --model=Post

This creates app/Policies/PostPolicy.php.

It includes default methods like view, update, delete, etc.

Step 6 — Define authorization rules

Open app/Policies/PostPolicy.php and edit it like this:

namespace App\Policies; use App\Models\Post; use App\Models\User; class PostPolicy { /** * Determine if the user can update the post. */ public function update(User $user, Post $post): bool { return $user->id === $post->user_id; } /** * Determine if the user can delete the post. */ public function delete(User $user, Post $post): bool { return $user->id === $post->user_id; } }

Only the author of the post can update or delete it.

Step 7 — Register the Policy

Open app/Providers/AuthServiceProvider.php and add this mapping:

protected $policies = [ \App\Models\Post::class => \App\Policies\PostPolicy::class, ];

Save the file.

Step 8 — Apply authorization in the controller

In PostController.php, add checks before update/delete:

public function edit(Post $post) { $this->authorize('update', $post); return view('posts.edit', compact('post')); } public function update(Request $request, Post $post) { $this->authorize('update', $post); $request->validate([ 'title' => 'required|max:255', 'content' => 'required', ]); $post->update($request->only('title', 'content')); return redirect()->route('posts.index') ->with('success', 'Post updated successfully.'); } public function destroy(Post $post) { $this->authorize('delete', $post); $post->delete(); return redirect()->route('posts.index') ->with('success', 'Post deleted successfully.'); }

Step 9 — Hide Edit/Delete Buttons in Views

Open resources/views/posts/index.blade.php
and wrap your buttons with @can directives:

@can('update', $post) <a href="{{ route('posts.edit', $post) }}" class="btn btn-sm btn-warning">Edit</a> @endcan @can('delete', $post) <form action="{{ route('posts.destroy', $post) }}" method="POST" class="d-inline" onsubmit="return confirm('Delete this post?');"> @csrf @method('DELETE') <button class="btn btn-sm btn-danger">Delete</button> </form> @endcan

Now, only the post owner sees these buttons.

Step 10 — Testing the Policy

  1. Log in as User A

  2. Create a post

  3. Log out and log in as User B

  4. Visit /posts → User B will not see edit/delete buttons for User A’s post

  5. If User B tries to access /posts/1/edit directly → Laravel will show a 403 Forbidden page

Policy is working successfully!

Summary

FeatureDescription
GatesSimple authorization logic using closures
PoliciesOrganized authorization for specific models
@can / @cannotBlade directives for UI control
authorize()Controller method for enforcing rules

You’ve Learned:

✔ How to create and register a Laravel Policy
✔ How to protect actions (edit, delete) by ownership
✔ How to show/hide UI based on permissions
✔ How to use Gates for simple cases

Souy Soeng

Souy Soeng

Hi there 👋, I’m Soeng Souy (StarCode Kh)
-------------------------------------------
🌱 I’m currently creating a sample Laravel and React Vue Livewire
👯 I’m looking to collaborate on open-source PHP & JavaScript projects
💬 Ask me about Laravel, MySQL, or Flutter
⚡ Fun fact: I love turning ☕️ into code!

Post a Comment

CAN FEEDBACK
close