---
title: Use Policies and Gates for Authorization
impact: HIGH
impactDescription: centralizes authorization logic, prevents scattered role checks, enables testing and auditing
tags: authorization, policy, gate, roles, security, laravel
---

## Use Policies and Gates for Authorization

Manual role checks scattered across controllers (`if ($user->role === 'admin')`) are fragile, hard to audit, and often inconsistently applied.

**Wrong:**

```php
// Controller — authorization logic throughout
public function update(Request $request, Post $post)
{
    if ($request->user()->id !== $post->user_id) {
        abort(403);
    }
    if ($request->user()->role !== 'editor' && $request->user()->role !== 'admin') {
        abort(403); // duplicated in show, destroy, etc.
    }
    $post->update($request->validated());
}
```

**Correct:**

```bash
php artisan make:policy PostPolicy --model=Post
```

```php
// app/Policies/PostPolicy.php
class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->isEditor();
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->isAdmin();
    }
}

// Controller — thin and readable
public function update(UpdatePostRequest $request, Post $post)
{
    $this->authorize('update', $post); // throws 403 if denied

    $post->update($request->validated());
}

// Or in Form Request
public function authorize(): bool
{
    return $this->user()->can('update', $this->route('post'));
}
```

**Gates for non-model actions:**
```php
// AppServiceProvider
Gate::define('access-reports', fn(User $u) => $u->hasRole('analyst'));

// Anywhere
if (Gate::denies('access-reports')) abort(403);
```
