Rely on Laravel's Authorization as much as possible
Publish Date: Apr 13 '23
0 0
Hi and welcome to this article where I will show you the benefit of Laravel's Authorization features and why it is important to stick to it as much as possible for everything related to access control.
Laravel offer an elegant way to perform access control verification within your app.
Let us say you build a tech news platform where your users can contribute by posting articles. Your users can create and edit blog posts.
The route to edit a blog post is GET /post/{post}/edit and the route to save the changes is PUT /post/{post}.
Now let us imagine a malicious user have found the id of the blog post of another user, and want to edit it on behalf of the author. You want to prevent this from happening and here is how you can do it with Laravel's policies.
As you can see, we instructed Laravel to protect our routes by checking if the user can edit posts (can:edit,post). Here can is a pre-registered middleware. You can view it on your route kernel file at "app/Http/Kernel.php":
This middleware assumes you either registered a Policy (which attach to an Eloquent model) or a Gate (which can be attached to anything).
Most of the time I write authorization around models, so I like to use Policies as much as possible. Here is the policy that would prevent other person than the author from editing a blog post:
namespaceApp\Policies;useApp\Models\Post;useApp\Models\User;useIlluminate\Auth\Access\Response;classPostPolicy{publicfunctionupdate(User$user,Post$post):Response{if($post->author->is($user)){return$this->allow();}return$this->deny("Only the author can edit this post.");}}
Finally, policies require you to register them to tell Laravel how it should link them automatically when using the Route middleware syntax.
On your file at "app/Providers/AuthServiceProvider.php":
Now let us imagine your tech news platform have a back office. You can get various statistics about how your app performs, you can view the blog post count, and other anaylitcs.
You only allow your team mates to browse this back office so you decide to use a permission system like spatie/laravel-permission.
For the moment you use two roles, "admin" and "manager". Admins can view all the back office and change the color of the website, but manager can only views pages without ability to change anything.
You protect these routes using the provided "role" middleware of this package:
namespaceApp\Policies;useApp\Models\User;useIlluminate\Auth\Access\Response;classUserPolicy{publicfunctionbefore(User$user):Response{if($$user->suspended){return$this->deny("Your account has been suspended");}returnnull;}}
Now we hit a roadblock: our authorization logic is now spread across two systems, permissions and policies. Let us fix this by keeping the UserPolicy and ditching any mention to permissions outside this policy:
namespaceApp\Policies;useApp\Models\User;useIlluminate\Auth\Access\Response;classUserPolicy{publicfunctionbefore(User$user):?Response{if($$user->suspended){return$this->deny("Your account has been suspended");}returnnull;}publicfunctionviewAdmin(User$user):Response{if($user->hasPermissionTo("view admin")){return$this->allow();}return$this->deny("You do not have the permission to view this page.");}publicfunctioneditSettings(User$user):Response{if($user->hasPermissionTo("edit settings")){return$this->allow();}return$this->deny("You do not have the permission to edit settings.");}}
Then, let us change the routes to rely on Laravel's authorization system instead:
By sticking to the built-in capabilities of Laravel, you ensure your app is easy to maintain and to evolve in the future.
If tomorrow you want to replace "spatie/laravel-permission" by another system (or building your own), the only part of the code you will change is the ÙserPolicy.
In the first part of this article I voluntarily open a nasty security breach by assuming users could discover the blog post edit route. In reality, you want to make this hard and I advice to expose UUIDs or any hard to predict identifier in your URLs instead of /post/34/edit. I have created a package to help you do the least possible while securing your route. Check it out.
By default, when getting a model from a controller using Route Model Binding, Laravel will try to find a model using the parameter in your route, and associate it to the default identifier of the related table (most of the time, this is the "id" key).
// routes/web.phpuseApp\Models\Cart;
useIlluminate\Support\Facades\Route;
// --> What you seeRoute::get("/cart/{cart}", function(Cart$cart) {
// $cart ready to be used
});
// --> What happens behind the sceneRoute::get("/cart/{cart}", function(string$identifier) {
$cart = Cart::findOrFail($identifier);
// $cart ready to be used
});
I hope this article helped you clarify the goal of Laravel Authorization and will help you make your app more robust. Please let me know your thoughts in the comments!