Authentication answers "Who are you?" β Authorization answers "What are you allowed to do?" Laravel provides two ways to authorize actions: Gates (simple closures) and Policies (organized by model). Think of Gates for quick checks and Policies for model-specific permissions.
1. Gates
Gates are simple closures that determine if a user can perform an action. Define them in AppServiceProvider.
app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Gate;
use App\Models\User;
use App\Models\Post;
public function boot(): void
{
// Simple gate: only admins can access
Gate::define('access-dashboard', function (User $user) {
return $user->role === 'admin';
});
// Gate with model: only the author can update a post
Gate::define('update-post', function (User $user, Post $post) {
return $user->id === $post->user_id;
});
// Gate: admin can do everything (before hook)
Gate::before(function (User $user, string $ability) {
if ($user->role === 'admin') {
return true; // Admin bypasses all gates
}
});
}Using Gates
use Illuminate\Support\Facades\Gate;
// In controller
public function edit(Post $post)
{
// Check permission β returns 403 if denied
Gate::authorize('update-post', $post);
return view('posts.edit', compact('post'));
}
// Check without throwing error
if (Gate::allows('update-post', $post)) {
// User can update
}
if (Gate::denies('update-post', $post)) {
// User cannot update
}
// In Blade templates
@can('update-post', $post)
<a href="/posts/${'{{'} $post->id ${'}}'}/edit">Edit</a>
@endcan
@cannot('update-post', $post)
<p>You cannot edit this post.</p>
@endcannot2. Policies
Policies organize authorization logic around a specific model. Each method in a policy corresponds to an action (view, create, update, delete).
Terminal
# Generate a policy for Post model
php artisan make:policy PostPolicy --model=Postapp/Policies/PostPolicy.php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
// Can the user view any posts?
public function viewAny(User $user): bool
{
return true; // Everyone can view posts list
}
// Can the user view this specific post?
public function view(User $user, Post $post): bool
{
return true; // Everyone can view a post
}
// Can the user create posts?
public function create(User $user): bool
{
return $user->role === 'admin' || $user->role === 'author';
}
// Can the user update this post?
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
// Can the user delete this post?
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}Auto-discovery: Laravel automatically discovers policies if you follow naming conventions:
Post model β PostPolicy. No manual registration needed. 3. Using Policies
In Controllers
app/Http/Controllers/PostController.php
class PostController extends Controller
{
public function index()
{
$this->authorize('viewAny', Post::class);
$posts = Post::all();
return view('posts.index', compact('posts'));
}
public function edit(Post $post)
{
// Throws 403 if user is not the author
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$post->update($request->validated());
return redirect()->route('posts.show', $post);
}
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return redirect()->route('posts.index');
}
}Via User Model
Using on User model
$user = Auth::user();
if ($user->can('update', $post)) {
// User can update this post
}
if ($user->cannot('delete', $post)) {
// User cannot delete this post
}In Blade Templates
Blade
@can('create', App\Models\Post::class)
<a href="/posts/create">Create Post</a>
@endcan
@can('update', $post)
<a href="/posts/${'{{'} $post->id ${'}}'}/edit">Edit</a>
@endcan
@can('delete', $post)
<form method="POST" action="/posts/${'{{'} $post->id ${'}}'}">
@csrf
@method('DELETE')
<button>Delete</button>
</form>
@endcan4. Authorization in Routes
routes/web.php
// Using 'can' middleware
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])
->middleware('can:delete,post');
// For actions without model instance
Route::get('/posts/create', [PostController::class, 'create'])
->middleware('can:create,App\Models\Post');5. Simple Role-Based Access
A practical example combining Gates for role-based access:
Migration: add role to users
// Add to users migration
$table->string('role')->default('user'); // user, author, adminAppServiceProvider β define role gates
Gate::define('admin', fn (User $user) => $user->role === 'admin');
Gate::define('author', fn (User $user) => in_array($user->role, ['author', 'admin']));
Gate::define('manage-users', fn (User $user) => $user->role === 'admin');Usage
// In routes
Route::middleware('can:admin')->group(function () {
Route::get('/admin', [AdminController::class, 'index']);
Route::resource('/users', UserController::class);
});
// In Blade
@can('admin')
<a href="/admin">Admin Panel</a>
@endcanSummary
- βGates β simple closure-based authorization checks
- βPolicies β model-specific authorization organized by actions
- β$this->authorize() β check in controllers (throws 403)
- β@can / @cannot β conditional rendering in Blade
- βcan: middleware β protect routes with authorization