Master Laravel Policies to secure your PHP applications. Learn how to move authorization logic out of controllers into clean, reusable, and testable classes.

I remember the first time I shipped a feature where the authorization logic lived directly inside a controller method. It was a simple "edit post" feature, but by the time I added checks for guest users, post ownership, and administrative overrides, the method was a mess of if/else statements. It was brittle, hard to test, and frankly, embarrassing to look at.
If you’re still putting if ($user->id !== $post->user_id) inside your controllers, you’re making your life harder than it needs to be. That’s where Laravel Authorization comes in. By using Policies, you can extract that logic into dedicated, manageable classes that keep your code clean and your security posture solid.
When I started out, I thought inline checks were fine because they were "easy." The reality is that as your project grows, you’ll need to perform the same checks in multiple places—the controller, the Blade view, and maybe even a CLI command. If you keep those checks inside the controller, you’ll eventually find yourself copy-pasting logic, which is a recipe for security bugs.
Laravel Policies provide a centralized home for your authorization rules. Instead of scattered logic, you get a clean class where each method maps to a specific action (like view, update, or delete). It’s much easier to reason about security when you can see all the rules for a resource in one file.

Let’s say we’re building a blog. We want to ensure that only the author of a post can update it. First, generate the policy using Artisan:
Bashphp artisan make:policy PostPolicy --model=Post
This creates a class in app/Policies/PostPolicy.php. Inside, you’ll see methods pre-filled for standard CRUD operations. Let’s implement the update check:
PHPpublic function update(User $user, Post $post): bool { return $user->id === $post->user_id; }
That’s it. It’s clean, expressive, and explicitly states the requirement. You don't need to manually register this policy if you're using modern Laravel versions; the framework discovers it automatically based on the model name.
Once your policy is defined, using it is trivial. You can call it directly from the controller using the $this->authorize() method provided by the AuthorizesRequests trait:
PHPpublic function update(Request $request, Post $post) { $this->authorize('update', $post); $post->update($request->validated()); return redirect()->route('posts.index'); }
If the user isn't authorized, Laravel automatically throws an AuthorizationException, which returns a 403 Forbidden response. This keeps your controller methods focused on handling the request rather than managing security permissions.
Sometimes, you don't have a model—maybe you're checking if a user has a specific "premium" subscription status or a global permission. For these non-resource-specific checks, you should reach for the Laravel Gate.
I usually define these in the boot method of my AuthServiceProvider. If you're wondering how that fits into your architecture, check out my thoughts on Laravel Service Providers: A Practical Guide to Clean Architecture to see how to keep your boot logic organized.
PHPGate::define('access-dashboard', function (User $user) { return $user->is_admin || $user->is_premium; });
You can then check this anywhere in your app:
PHPif (Gate::allows('access-dashboard')) { #6A9955">// Show the dashboard }
Early on, I made the mistake of putting complex database queries inside my policy methods. Don't do this. Policies should be fast. If you need to check a complex permission involving three different tables, you're better off refactoring that logic into a dedicated service class.
Also, don't forget that policies are just PHP classes. You can inject dependencies into the constructor if needed, or use them alongside Laravel Middleware: A Practical Guide to Request Filtering to handle high-level access control before the request even hits your controller.
If you're still struggling with bugs, it’s worth reviewing 7 Laravel errors every beginner hits (and how to fix them) to ensure your base routing and model binding aren't interfering with your authorization checks.

What if I need to bypass authorization for admins?
You can define a before method in your policy. If this method returns a boolean, that result will be used for all authorization checks in that policy, allowing you to "short-circuit" the logic for super-admins.
Can I use policies in Blade templates?
Absolutely. Use the @can directive: @can('update', $post) <button>Edit</button> @endcan. It’s the cleanest way to hide UI elements that the user isn't allowed to interact with.
Is it overkill for small projects? Honestly, it takes about two minutes to set up a policy. Even in small projects, having a dedicated place for PHP security logic prevents the "where did I put that check?" headache later on.
I’m still refining how I handle complex role-based access control (RBAC). Sometimes, policies get a bit bloated when you have five different roles with overlapping permissions. Next time, I might experiment with dedicated "Permission" classes instead of putting everything in the policy, but for 90% of cases, the standard approach works perfectly. Keep it simple, test your authorization paths, and don't let your controllers do the heavy lifting.
Structuring a Laravel package correctly prevents technical debt. Learn how to organize your files, manage dependencies, and write code that lasts.
Read more