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.

Medium severity Application Security Updated 2026-05-01

The Problem

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.

How to Fix

  1. 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. 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. 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());
                }
            });
        }
    }

How to Verify

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.

Related Security Terms

Detect This Automatically with StackShield

StackShield continuously monitors your Laravel application from the outside and alerts you when security issues are found. No installation required.

Start Free Trial