Implementing Laravel Policies for Robust Authorization
Learn to implement Laravel policies to centralize and simplify your authorization logic. Secure your project board resources with clean, reusable access rules.
Previously in this course, we explored Implementing Middleware for API Security in Laravel to verify user ownership at the route level. While middleware is excellent for broad access control, it often leads to code duplication when your authorization logic grows complex. Today, we’ll move beyond middleware by implementing policies, a dedicated layer for granular, domain-specific authorization.
Why Policies?
In our project board application, we need to decide if a user can update a task, delete a project, or add a team member. If we keep this logic inside controllers, they become bloated, hard to test, and difficult to maintain.
Laravel policies allow us to encapsulate authorization logic for specific Eloquent models into dedicated classes. This is the "Goldilocks" zone of security: it’s more flexible than simple middleware and more readable than scattered if statements throughout your service layer.
Generating and Implementing Policies
To start, generate a policy for our Task model using Artisan:
Bashphp artisan make:policy TaskPolicy --model=Task
This creates app/Policies/TaskPolicy.php. Let's implement a rule that only the task creator or a project manager can update a task.
PHPnamespace App\Policies; use App\Models\Task; use App\Models\User; class TaskPolicy { public function update(User $user, Task $task): bool { #6A9955">// A user can update if they own the task #6A9955">// or if they are the project owner return $user->id === $task->user_id || $user->id === $task->project->user_id; } }
Laravel automatically discovers this policy because it follows the naming convention (Task -> TaskPolicy). You don't need to manually register it unless you are using a custom structure.
Authorizing Actions in Controllers
With the policy defined, we no longer need to manually check if ($user->id !== $task->user_id) in our controller. Instead, we use the authorize method provided by the AuthorizesRequests trait (included by default in base controllers).
Refactoring our TaskController becomes trivial:
PHPpublic function update(Request $request, Task $task) { #6A9955">// Throws an AuthorizationException if the policy returns false $this->authorize('update', $task); $this->taskService->updateTask($task, $request->validated()); return response()->json(['message' => 'Task updated successfully']); }
If the user isn't authorized, Laravel automatically throws an Illuminate\Auth\Access\AuthorizationException, which we handle globally as discussed in our lesson on Mastering Laravel Exception Handling.
Hands-on Exercise: Implementing Project Deletion
- Generate a
ProjectPolicyfor yourProjectmodel. - Define a
deletemethod in that policy. A project should only be deletable by the user who created it. - Apply the
$this->authorize('delete', $project)call inside yourProjectController@destroymethod. - Verify the behavior by attempting to delete a project as a user who doesn't own it; ensure you receive a
403 Forbiddenresponse.
Common Pitfalls
- Forgetting to pass the Model instance: Always pass the instance, not the class name, when checking instance-specific authorization (e.g.,
$this->authorize('update', $task)). Use the class name only for "create" actions (e.g.,$this->authorize('create', Task::class)). - Over-complicating policies: Policies should be simple boolean checks. If your authorization logic requires complex database queries or external API calls, consider moving that logic into a dedicated "Guard" service and calling it from your policy.
- Ignoring User/Guest states: Remember that
$usercan be null if you allow guest access to certain endpoints. Always type-hint?Userif your policy needs to handle unauthenticated users.
Recap
We’ve moved authorization out of our controllers and into dedicated, testable policy classes. By leveraging authorize(), we keep our controllers thin and our security logic centralized. This approach makes our project board significantly more maintainable as we add features like team roles and permissions.
Up next: We'll dive into Customizing Authentication Guards to support multi-auth scenarios, such as separating API tokens from web-based sessions.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.