Policies for Complex Authorization in Laravel

Authorization is one of the most important responsibilities of any modern web application. Once users are authenticated, the next crucial step is defining what each user is allowed to do. In Laravel, authorization can be handled using Gates or Policies. While Gates are perfect for simple checks, Policies are designed specifically for complex, model-based authorization. A Policy acts like a dedicated authorization class where you define permissions for actions on a specific model such as viewing, creating, updating, or deleting a resource.

This 3000-word guide will walk you through every aspect of Laravel Policies, including what they are, why they are essential, how to create them, how they work internally, how to register and call them, how to integrate them with controllers and views, how to use helper methods, how to handle authorization failures, and how to combine Policies with roles and permissions.

By the end of this article, you will understand the full power of Laravel’s Policy system and how to use it for scalable and maintainable authorization logic.

Understanding the Need for Policies in Laravel

Policies organize authorization logic based on models. For example, if your application manages Posts, Comments, Products, Orders, or Users, each resource may require complex permission rules. Policies help centralize and structure these rules.

A Post model might need the following logic:

  • A user can only update their own posts
  • An admin can update any post
  • A user can only delete posts they created
  • A user can view only posts marked as public or posts they own
  • A user with a specific role can publish posts

Instead of writing these checks inside controllers, routes, or views, Policies store them in one dedicated location.

This promotes:

  • Cleaner code
  • Better maintainability
  • Strong separation of concerns
  • Easier testing
  • Scalability as the application grows

Policies encourage writing authorization logic in an organized, reusable, and readable structure.


What Exactly Is a Policy in Laravel

A Policy is a PHP class containing authorization methods. Each method represents permission for a specific action on a model.

Typical Policy methods:

view
create
update
delete
restore
forceDelete

Each method returns true or false depending on whether the user is allowed to perform the action.

Example:

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

This checks whether the logged-in user is the owner of the post.


Creating a Policy With Artisan

Laravel provides a simple command to create a policy:

php artisan make:policy PostPolicy --model=Post

This generates:

app/Policies/PostPolicy.php

Inside the file, Laravel automatically includes methods for all standard actions:

public function view(User $user, Post $post) {}
public function create(User $user) {}
public function update(User $user, Post $post) {}
public function delete(User $user, Post $post) {}

This is a full structured class ready for customization.


Understanding the Generated Policy Structure

The default structure looks like:

class PostPolicy
{
public function viewAny(User $user) {}
public function view(User $user, Post $post) {}
public function create(User $user) {}
public function update(User $user, Post $post) {}
public function delete(User $user, Post $post) {}
public function restore(User $user, Post $post) {}
public function forceDelete(User $user, Post $post) {}
}

Each method has a clear responsibility.

For example:

  • viewAny = permission to view a list of posts
  • view = permission to view a specific post
  • create = permission to create a post
  • update = permission to edit a post
  • delete = permission to remove a post

You can remove unused methods or add your own if needed.


How Laravel Automatically Associates a Policy With a Model

When you create a Policy using:

php artisan make:policy PostPolicy --model=Post

Laravel automatically registers the policy in:

app/Providers/AuthServiceProvider.php

Example registration:

protected $policies = [
Post::class => PostPolicy::class,
];

This tells Laravel:

Whenever authorization is requested for the Post model, use the PostPolicy class.

This automatic mapping is how Laravel knows what Policy to apply.


Writing Authorization Logic Inside Policy Methods

Let us explore examples for each type of action.

View Policy

public function view(User $user, Post $post)
{
return $post->is_public || $user->id === $post->user_id;
}

Allows viewing if the post is public or owned.

Create Policy

public function create(User $user)
{
return $user->role === 'author';
}

Allows creating new posts only for authors.

Update Policy

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

Only the post owner can update.

Delete Policy

public function delete(User $user, Post $post)
{
return $user->id === $post->user_id || $user->role === 'admin';
}

Owners and admins can delete posts.

These examples demonstrate how Policies allow writing expressive authorization logic.


Using Policies in Controllers

Inside controllers, you authorize actions using the authorize method.

Example:

public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$post->update($request->all());
return redirect()->back();
}

If the user is not allowed, Laravel automatically throws:

403 Unauthorized

Short syntax:

$this->authorize('delete', $post);

This makes controller logic clean and readable.


Using Policies in Routes

You can also authorize directly in route definitions:

Route::put('/posts/{post}', function (Post $post) {
$this->authorize('update', $post);
});

Or using middleware:

Route::delete('/posts/{post}', function (Post $post) {
//
})->middleware('can:delete,post');

Using Policies in Blade Views

Blade provides helpful directives for policy-based checks.

Example:

@can('update', $post)
<a href="/posts/{{ $post->id }}/edit">Edit</a>
@endcan

Disable buttons for unauthorized users:

@cannot('delete', $post)
<p>You cannot delete this post.</p>
@endcannot

Show content only if allowed:

@can('view', $post)
<h1>{{ $post->title }}</h1>
@endcan

Blade syntax makes authorization easy within views.


Authorizing Actions Without a Model Instance

Sometimes, you authorize an action that is not related to a specific record.

For example, creating a post:

$this->authorize('create', Post::class);

In Blade:

@can('create', App\Models\Post::class)
<a href="/posts/create">Add New Post</a>
@endcan

This checks the create method in the Policy.


Using Helper Functions for Authorization

Laravel offers helper methods:

Gate::allows('update-post', $post)
Gate::denies('update-post', $post)

Or inside policies:

$user->can('update', $post)
$user->cannot('update', $post)

These simple checks are useful in custom logic.


Handling Authorization Failures

If authorization fails using authorize(), Laravel throws:

Illuminate\Auth\Access\AuthorizationException

You can catch it:

try {
$this->authorize('update', $post);
} catch (\Exception $e) {
return redirect()->back()->withErrors('Not allowed');
}

Or customize the error view.


Combining Policies With Roles and Permissions

If your app uses roles (admin, editor, author, viewer), Policies can include role logic.

Example:

if ($user->role === 'admin') {
return true;
}

Or for Spatie Permission package:

if ($user->can('edit posts')) {
return true;
}

This is powerful for complex authorization setups.


Writing Advanced Policy Conditions

You can implement multi-layer checks:

public function update(User $user, Post $post)
{
if ($user->role === 'admin') {
    return true;
}
if ($post->locked) {
    return false;
}
return $user->id === $post->user_id;
}

Conditions can be based on:

  • model fields
  • user roles
  • time of the day
  • subscription status
  • content ownership

Policies allow any level of complexity.


Policy Method Naming Conventions

Policies use predefined method names:

viewAny
view
create
update
delete
restore
forceDelete

You can also create custom actions:

public function publish(User $user, Post $post)
{
return $user->role === 'editor';
}

Use:

$this->authorize('publish', $post);

Policy Auto-Discovery

Laravel auto-discovers policies if:

  • Model is in app/Models
  • Policy is in app/Policies
  • Follows naming: PostPolicy for Post

Then manual registration is not required.


Multiple Actions in One Policy

A single Policy can handle multiple related tasks:

publish  
unpublish  
feature  
archive  
pin  
approve  
reject  

All inside one PostPolicy.

This prevents scattered authorization code.


Policies and Soft Deletes

You can create rules for restore and forceDelete:

public function restore(User $user, Post $post)
{
return $user->role === 'admin';
} public function forceDelete(User $user, Post $post) {
return $user->role === 'admin';
}

Soft deleted models have special authorization needs.


Testing Policies

Laravel makes policy testing simple.

Example:

$user = User::factory()->create();
$post = Post::factory()->create(['user_id' => $user->id]);

$this->assertTrue($user->can('update', $post));

Testing ensures authorization logic works before production.


Policy Organization Best Practices

Follow these guidelines:

  • Use one Policy per model
  • Keep logic simple
  • Check user roles early
  • Avoid duplicating conditions
  • Use helper methods when necessary
  • Document your policy rules
  • Write tests for each method

Proper structure ensures easy maintenance.


When to Use Policies Instead of Gates

Use Policies when:

  • Authorization applies to a specific model
  • Multiple actions need rules
  • You need organized logic
  • You want to use create, update, delete, view patterns
  • Your app grows large

Use Gates only for simple checks not tied to a model.


Common Mistakes Developers Make With Policies

Avoid the following:

  • Forgetting to return a boolean
  • Writing logic inside controllers instead of policies
  • Using user IDs directly in controllers
  • Forgetting to register custom policies
  • Overcomplicating rules without separating into methods

Policies need to be clean and focused.


Real-World Example: Blog Application

A blog app may have the following rules:

  • Admins can do everything
  • Editors can update any post
  • Authors can update only their own posts
  • Guests can view only published posts
  • Only admins can delete posts
  • Authors cannot view drafts of others

Policies make it easy to implement such rules.

Example:

public function view(User $user, Post $post)
{
if ($user->role === 'admin') return true;
if ($post->is_published) return true;
return $user->id === $post->user_id;
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *