# Laravel Missing Authorization: How to Add Policy and Gate Checks to Controllers

> Controllers with store, update, and destroy actions but no authorization let any authenticated user modify any record. Add policies to enforce ownership.

**Severity:** medium | **Category:** Application Security

---

## The Issue

Authentication verifies who a user is. Authorization verifies what they can do. A controller that checks authentication (auth middleware) but not authorization lets any logged-in user create, edit, or delete any record — including other users' data. This is called an Insecure Direct Object Reference (IDOR) and is consistently in the OWASP Top 10. If your PostController lets user A update user B's posts, you have a missing authorization vulnerability.

## Steps to Fix

### 1. Create a policy for each model

Generate policies for models that need access control:

php artisan make:policy PostPolicy --model=Post

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

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

public function create(User $user): bool
{
    return true; // Any authenticated user can create
}

### 2. Apply authorization in controllers

Use the authorize() method or Gate facade:

// Using authorize() — throws 403 if denied
public function update(Request $request, Post $post)
{
    $this->authorize('update', $post);
    // ... update logic
}

// Using middleware for all actions
public function __construct()
{
    $this->authorizeResource(Post::class, 'post');
}

// Using Gate directly
if (Gate::denies('update', $post)) {
    abort(403);
}

### 3. Scope queries to the authenticated user

In addition to policies, scope database queries to prevent data leakage:

// WRONG — returns any user's post
$post = Post::findOrFail($id);

// RIGHT — only returns the authenticated user's post
$post = auth()->user()->posts()->findOrFail($id);

// Or use a global scope
class Post extends Model
{
    protected static function booted()
    {
        static::addGlobalScope('owned', function (Builder $builder) {
            if (auth()->check()) {
                $builder->where('user_id', auth()->id());
            }
        });
    }
}

## Verification

Test by logging in as User A and trying to access User B's resources:

# Get User B's post ID, then try to update it as User A
curl -X PUT https://yourapp.com/api/posts/123 \
  -H 'Authorization: Bearer USER_A_TOKEN' \
  -d 'title=Hacked'

This should return 403 Forbidden, not 200 OK. Run php artisan stackshield:scan --check=SS053 to find controllers missing authorization.

## Prevention

Use authorizeResource() in controller constructors for automatic policy mapping. Add authorization checks to your code review checklist. Write feature tests that verify User A cannot access User B's resources. Use StackShield to scan for controllers missing authorization.

---

## Frequently Asked Questions

### Is auth middleware enough for authorization?

No. Auth middleware only verifies the user is logged in. It does not check whether that specific user has permission to perform the requested action on the requested resource. You need both: auth middleware (who) and policies/gates (what).

### Should I use policies or gates?

Use policies when authorization logic is tied to a specific model (PostPolicy for Post). Use gates for general abilities not tied to a model (Gate::define('manage-settings')). Most CRUD controllers should use policies.

