DEV Community

A0mineTV
A0mineTV

Posted on

Laravel Policies: Centralize Your Authorization and Keep Your Controllers Clean

Authorization often starts simple, but as a project grows, access rules quickly become duplicated and inconsistent across controllers, Blade views, and API endpoints. Laravel Policies solve this by providing a single, explicit place to define who can do what on a given resource.

At the beginning, many Laravel apps handle access rules with quick checks:

  • an if in a controller,
  • another check in a Blade view,
  • maybe a condition in a service,
  • and (sometimes) a missing check on an API endpoint.

A few weeks later, the rules are duplicated, inconsistent, and hard to change safely.

Laravel Policies solve that by giving you a single, explicit place to define:

Who can do what on a given model (resource).


What is a Policy in Laravel?

A Policy is a class that groups authorization logic around a specific model.

Examples:

  • ProjectPolicy → rules for Project
  • PostPolicy → rules for Post
  • OrderPolicy → rules for Order

A Policy answers questions like:

  • “Can this user update this project?”
  • “Can they delete this post?”
  • “Can they view this order?”

Instead of scattering checks everywhere, you put the rule once in the Policy, then reuse it consistently with authorize() / can().


Why Policies matter (practical reasons)

1) One source of truth

One rule = one place.

No more hunting through controllers and views to find all the conditionals.

2) Consistent across the app

The same rule applies to:

  • web controllers,
  • API controllers,
  • Blade templates,
  • jobs,
  • commands.

3) Less security risk

When authorization is standardized, it’s harder to “forget” a check on one route.

4) Easier to evolve

Rules change all the time (“managers can edit too”, “only premium users can do X”).

With Policies, you update the logic once.


A simple example: “Owner can update, admin can do everything”

Let’s say a Project belongs to a user. We want:

  • admins can update any project,
  • owners can update their own project.

Policy

// app/Policies/ProjectPolicy.php
namespace App\Policies;

use App\Models\Project;
use App\Models\User;

class ProjectPolicy
{
    public function update(User $user, Project $project): bool
    {
        return $user->is_admin || $project->user_id === $user->id;
    }
}
Enter fullscreen mode Exit fullscreen mode

Use it in a controller

// app/Http/Controllers/ProjectController.php
use App\Models\Project;

class ProjectController
{
    public function update(Project $project)
    {
        $this->authorize('update', $project);

        // ... update logic
        return back();
    }
}
Enter fullscreen mode Exit fullscreen mode

That’s the core workflow:

  1. Your controller asks Laravel: “is this allowed?”
  2. Laravel calls the Policy method (update)
  3. The Policy returns true or false

How does Laravel find the right Policy?

Laravel maps models to policies.

Option A: Auto discovery

If your Policy is named like ProjectPolicy and placed in app/Policies, Laravel can often discover it automatically (depending on your version/config).

Option B: Explicit mapping

You can register Policies in your AuthServiceProvider:

// app/Providers/AuthServiceProvider.php
namespace App\Providers;

use App\Models\Project;
use App\Policies\ProjectPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        Project::class => ProjectPolicy::class,
    ];

    public function boot(): void
    {
        $this->registerPolicies();
    }
}
Enter fullscreen mode Exit fullscreen mode

Using Policies outside controllers

Policies aren’t just for controllers.

In Blade

@can('update', $project)
  <a href="...">Edit</a>
@endcan
Enter fullscreen mode Exit fullscreen mode

In code (services, jobs, anywhere)

if (auth()->user()->can('update', $project)) {
    // allowed
}
Enter fullscreen mode Exit fullscreen mode

A clean pattern for real projects

A simple rule helps:

✅ Controllers orchestrate, Policies decide

  • Controllers: receive request, call authorize, run the action.
  • Policies: contain the business rule of access.
  • Services/UseCases: do the actual work (optional, depending on your architecture).

This keeps responsibilities clear and prevents “permission spaghetti”.


Tips for writing good Policies

  • Keep methods small and explicit (view, create, update, delete).
  • Prefer readable conditions over clever logic.
  • Use relationships cleanly ($project->team_id, $user->teams->contains(...)).
  • Consider “role + ownership + status” rules (very common in SaaS).

Quick checklist

  • [ ] Every write action uses authorize() in controllers
  • [ ] Policies exist for core models (Project, Post, Order…)
  • [ ] Views use @can instead of ad-hoc checks
  • [ ] Mapping is configured (auto or AuthServiceProvider)
  • [ ] Tests cover a few critical permissions

Wrap-up

Laravel Policies are one of the best ways to keep authorization:

  • centralized (one rule, one place),
  • consistent (web, API, views),
  • safer (fewer forgotten checks),
  • easy to evolve.

Top comments (0)