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 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
- 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.
- 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.
- 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.
- 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.
A Guide to Tailwind CSS with Laravel for Developers
Learn how to use Tailwind CSS in Laravel to build modern, responsive web applications. This guide covers setup, SEO benefits, and best practices.
Florentin Pomirleanu
Principal Laravel Consultant
Laravel Flux: A Guide to the Official Livewire UI Kit
Explore Laravel Flux, the official Livewire component library. Learn about its features, the differences between the free and Pro versions, and how to use it.
Florentin Pomirleanu
Principal Laravel Consultant
A Complete Guide to Laravel Actions for Cleaner Code
Learn how to refactor your Laravel application using Actions. This guide covers implementation, real-world examples, and best practices.
Florentin Pomirleanu
Principal Laravel Consultant
Laravel consulting
Need senior Laravel help for this topic?
Let's adapt these practices to your product and deliver the next milestone.