Mar 30, 2022 by Thibault Debatty | 5219 views
https://cylab.be/blog/210/laravel-actionaccess-control-with-policies
When developing a web application with Laravel, you will usually have to deal with different users, that have different permissions. If the application is quite simple, with only 2 types of users (administrators that are almighty and regular users that have no permission) you can use a middleware to protect your administrator’s area. In this blog post we look at Laravel policies, that allow fine-grain control of user actions.
In your Laravel app, you probably have multiple controllers and actions, like PostController@update
for example. If your application supports multiple users, you must (probably) ensure that a user can only update his own Post.
A quick (and dirty) to implement this control is in the Controller itself:
public function update(Request $request, Post $post)
{
if ($post->user_id !== Auth::id) {
abort(403); // Unauthorized
}
// rest of processing
}
As time goes and the application grows, this will clutter the code and cause (security) bugs.
The other approach is to factor the access control code out of your controller, into a dedicated Policy class.
The PostPolicy class will contain the access control code:
class PostPolicy
{
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
And the PostController contains a single line to perform the verification:
public function update(Request $request, Post $post)
{
// check that the current user is authorized to update this post
$this->authorize('update', $post);
// rest of processing
}
Moreover, as you might guess from the example above, Laravel automatically determines the appropriate Policy class to call: the line $this->authorize('update', $post);
will eventually call the Policy method PostPolicy@update
.
This will make your code much easier to read and maintain!
However, there is one catch: in Laravel Policies are mapped to Models, not to Controllers. We will see the consequences of this in the example below…
The easiest way to create a Policy is using artisan:
php artisan make:policy PostPolicy
Your new Policy class will be stored in /App/Policies.
As mentioned above, in Laravel the Policy class is automatically determined, based on the Model that the user acts on. This mapping must be defined in /App/Providers/AuthServiceProvider.php:
<?php
namespace AppProviders;
use IlluminateFoundationSupportProvidersAuthServiceProvider as
ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*/
protected $policies = [
AppPost::class => AppPoliciesPostPolicy::class,
];
/**
* Register any authentication / authorization services.
*/
public function boot()
{
$this->registerPolicies();
}
}
Now that the Policy is registered, we may add the methods that will check the actions that a user is allowed to execute (or not). Each method takes an instance of User
as first argument and must return a boolean. For example, in /App/Policies/PostPolicy.php:
<?php
namespace AppPolicies;
use AppPost;
use AppUser;
class PostPolicy
{
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
In your code, the easiest way to call a Policy method is using authorize
. The first argument is the name of the Policy method, and the second is the Model object to validate:
public function update(Request $request, Post $post)
{
// check that the current user is authorized to update this post
$this->authorize('update', $post);
// rest of processing
}
Obviously, you can use the same Policy method at multiple places in your code:
/**
* Show the form for editing the specified Post.
*/
public function edit(Post $post)
{
$this->authorize('update', $post);
return view("post.edit", ["post" => $post]);
}
In some cases, the controller action has no instance of a Model to act on:
/**
* Show the form for creating a new Post.
*/
public function create()
{
return view("post.create");
}
As you may remember, Laravel uses the Model class to find the appropriate Policy class. In those cases, the second parameter of the authorize method must be the related class:
/**
* Show the form for creating a new Post.
*/
public function create()
{
// check that this user is authorized to create a Post
$this->authorize("create", Post::class);
return view("post.create");
}
In the PostPolicy class (in /App/Policies/PostPolicy.php), the corresponding method takes a single User argument:
public function create(User $user)
{
return $user->isAuthor();
}
Policies are described with much more details on the documentation page of Laravel:
https://laravel.com/docs/8.x/authorization#creating-policies
This blog post is licensed under CC BY-SA 4.0