Back to all articles
Laravel Insights Jan 24, 2026 โˆ™ 1 min read

A Developer's Guide to Laravel Policies for Authorization

Master Authorization in Laravel with Policies: A Step-by-Step Guide with Real-World Examples

A digital illustration of a key fitting into a lock, symbolizing security and authorization within the Laravel framework.

A Practical Guide to Laravel Policies

Authorization is a critical layer of security in any web application. It ensures that users can only perform actions they are permitted to execute. Laravel provides a robust and elegant authorization system through Policies, which organize authorization logic around specific models or resources.

Implementing a clear authorization strategy keeps your codebase clean, secure, and maintainable. This guide offers a comprehensive look at Laravel Policies, from creation and registration to real-world application. We will provide full code examples to demonstrate how to secure your models and streamline your application's security logic.

What Are Laravel Policies?

Policies are PHP classes that group authorization logic for a particular model. Instead of scattering if statements related to user permissions throughout your controllers, you can centralize these rules in a dedicated policy class. This approach provides a clean, object-oriented way to manage who can do what.

For instance, for a Post model, you might have a PostPolicy that contains methods to determine if a user can:

  • View a specific post (view).
  • Create a new post (create).
  • Update an existing post (update).
  • Delete a post (delete).

This separation of concerns makes your controllers leaner and your authorization logic reusable and easier to test.

Creating and Registering a Policy

Laravel's Artisan command-line tool makes it simple to generate a new policy. To create a policy for a Post model, you can run the following command.

php artisan make:policy PostPolicy --model=Post

The --model=Post option is a convenient shortcut. It generates a policy class in the app/Policies/ directory with pre-defined methods for common actions: viewAny, view, create, update, delete, restore, and forceDelete.

Policy Registration

Once a policy is created, you must register it so Laravel knows to use it for the corresponding model. This is done in the AuthServiceProvider.

By default, Laravel can automatically discover policies as long as they follow the standard naming convention (Post model -> PostPolicy) and reside in the default app/Policies directory. No manual registration is needed.

However, if you use a non-standard name or location, you must manually map the model to its policy in the $policies array of your app/Providers/AuthServiceProvider.php.

<?php

namespace App\Providers;

use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    /**
     * Register any authentication / authorization services.
     */
    public function boot(): void
    {
        $this->registerPolicies();
    }
}

Writing Policy Methods: A Real-World Example

Let's build out the PostPolicy to handle different user roles and permissions. Assume our User model has a role attribute (e.g., 'admin', 'editor', 'author').

app/Policies/PostPolicy.php

<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class PostPolicy
{
    use HandlesAuthorization;

    /**
     * Run before any other authorization checks.
     */
    public function before(User $user, string $ability): ?bool
    {
        // Admins can do anything
        if ($user->role === 'admin') {
            return true;
        }

        return null; // Let other policy methods decide
    }

    /**
     * Determine whether the user can view any models.
     */
    public function viewAny(User $user): bool
    {
        // Any authenticated user can see the list of posts.
        return true;
    }

    /**
     * Determine whether the user can view the model.
     */
    public function view(User $user, Post $post): bool
    {
        // Any user can view a published post.
        if ($post->is_published) {
            return true;
        }

        // Only the author can view their own unpublished draft.
        return $user->id === $post->user_id;
    }

    /**
     * Determine whether the user can create models.
     */
    public function create(User $user): bool
    {
        // Admins, editors, and authors can create posts.
        return in_array($user->role, ['admin', 'editor', 'author']);
    }

    /**
     * Determine whether the user can update the model.
     */
    public function update(User $user, Post $post): bool
    {
        // Editors can update any post. The admin check is handled by the `before` method.
        if ($user->role === 'editor') {
            return true;
        }

        // Authors can only update their own posts.
        return $user->id === $post->user_id;
    }

    /**
     * Determine whether the user can delete the model.
     */
    public function delete(User $user, Post $post): bool
    {
        // Only authors can delete their own posts. Admins are handled by `before`.
        return $user->id === $post->user_id;
    }
}

Key Points:

  • The before() method acts as a gatekeeper. If it returns true, the user is authorized, and no other policy methods are checked. If it returns false, the user is denied. Returning null passes the check to the specific policy method.
  • Each method receives the currently authenticated $user as its first argument. Subsequent arguments are the model instances.
  • The logic returns true for authorized actions and false for unauthorized ones.

Using Policies in Your Application

With the policy defined, you can enforce its rules in controllers, routes, and Blade views.

In Controllers

The AuthorizesRequests trait, included in Laravel's base controller, provides the authorize method. It's the most common way to use policies.

If the authorization check fails, authorize() automatically throws an AuthorizationException, which results in a 403 Forbidden HTTP response.

app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function update(Request $request, Post $post)
    {
        // This will check the 'update' method in PostPolicy
        $this->authorize('update', $post);

        // If authorization passes, continue with the update
        $post->update($request->all());

        return redirect()->route('posts.show', $post);
    }
    
    public function store(Request $request)
    {
        // For actions without a model instance, pass the class name
        $this->authorize('create', Post::class);

        // Logic to store a new post
        $newPost = Post::create([
            'user_id' => $request->user()->id,
            // ... other validated data
        ]);

        return redirect()->route('posts.show', $newPost);
    }
}

For resource controllers, you can authorize all methods in the constructor for maximum efficiency.

public function __construct()
{
    // This automatically maps resource methods to policy methods
    // index -> viewAny, show -> view, create -> create, store -> create, etc.
    $this->authorizeResource(Post::class, 'post');
}

In Blade Views

You can conditionally display UI elements based on user permissions using the @can and @cannot Blade directives. This keeps your interface clean and prevents users from seeing actions they cannot perform.

resources/views/posts/show.blade.php

<div>
    <h1>{{ $post->title }}</h1>
    <p>{{ $post->body }}</p>

    <div class="actions">
        {{-- Show the edit button only if the user can update the post --}}
        @can('update', $post)
            <a href="{{ route('posts.edit', $post) }}">Edit Post</a>
        @endcan

        {{-- Show the delete button only if the user can delete the post --}}
        @can('delete', $post)
            <form method="POST" action="{{ route('posts.destroy', $post) }}">
                @csrf
                @method('DELETE')
                <button type="submit">Delete Post</button>
            </form>
        @endcan
    </div>
</div>

In Routes

You can also apply authorization directly at the routing level using the can middleware. This is useful for protecting routes before the controller is even instantiated.

routes/web.php

use App\Http\Controllers\PostController;

// The user must pass the 'update' policy check for the given {post} model
Route::put('/posts/{post}', [PostController::class, 'update'])
     ->middleware('can:update,post');

// The user must pass the 'create' policy for the Post model class
Route::get('/posts/create', [PostController::class, 'create'])
     ->middleware('can:create,App\Models\Post');

Best Practices for Using Policies

  1. Keep Policies Focused: Each policy should be responsible for a single model. If authorization logic becomes too complex, consider if your model is doing too much.
  2. Use the before Method for Super Users: The before method is the perfect place to grant universal access to administrators, reducing redundant checks in other methods.
  3. Combine with Roles and Permissions: For complex applications, integrate policies with a role-based access control (RBAC) package like spatie/laravel-permission. Your policy methods can then check for specific roles or permissions.
  4. Authorize in the Controller, Hide in the View: Always perform authorization checks in the controller or route middleware as your primary line of defense. Use Blade directives as a secondary layer to improve the user experience, not as your only security measure.

Conclusion

Laravel Policies provide an efficient, organized, and scalable framework for managing authorization. By centralizing your permission logic into dedicated classes, you create a more secure and maintainable application. This clear separation of concerns keeps your controllers focused on handling HTTP requests and your authorization rules easy to find, test, and update. Adopting policies is a proven strategy for building robust applications that can adapt as business requirements evolve.


Related articles

Continue exploring Laravel insights and practical delivery strategies.

Laravel consulting

Need senior Laravel help for this topic?

Let's adapt these practices to your product and deliver the next milestone.