Laravel Gate & Policy, Part — 1: Understanding Laravel Gate
There is a part two of this article. You can find it HERE.
The documentation of Laravel Authorization is pretty straightforward & easy. If you haven’t seen it yet, it’s okay. I will try to rephrase the same thing with my own words. I hope you’ll get to know this topic thoroughly. Let’s jump into it.
Few notes
- Authorization & Authentication are different. The facade
Auth
is for authentication. Which validates the credentials with the stored data. If the user fails to validate the credential, the HTTP status code will be:401
- Authorization, on the other hand, means if you’re allowed to perform a task or not. In movies, we see that someone intrudes himself into a system he was not authorized bypassing some security flaws. That is called authorization. If you are authorized means you’re allowed. The HTTP status code for Unauthorized is
403
. - Authorization always takes part after the Authentication. If a system can’t identify who you’re, then how’ll it decide if you’re allowed or not?
How to authorize?
In Laravel, there are two ways to authorize a user. One approach is closure based, another approach is class-based. Closure based approach is called Gates. The class-based approach is called policy. It this article I’ll only mention Gates. The gates approach is more like the closure based route definition.
Closure based approach aka Gates
Closure based approach uses the Gate
Facade to define the authorizations. The facade proxies the calls toIlluminate/Auth/Access/Gate.php
. A sample code is like below. You can add the following snippet anywhere you wish. But it’s a good approach to keep the same type of code altogether. So, it’s good to use app/Providers/AuthServiceProvider.php
‘s boot
method to put in which is already shipped with Laravel. In closure
based approach, you use define
method to declare new authorization criteria which takes two parameters. The first one is the name, which will be used later as a reference to authorize the user. And secondly the closure itself. Remember one thing that the closure
gets the currently logged in user by default in its first param.
use Illuminate\Support\Facades\Gate;Gate::define('create-post', function ($user) {
return $user->id == 1;
});
In the above example, we’re allowing only the user id 1 to have the authority to create a post. Other than that, nobody will have the authorization to add a new post to your website.
To check if the logged-in user is allowed to create a new post, you need to call like following.
Gate::allows('create-post');
That will return a boolean value denoting if the user is allowed or rejected. Simple, right? Do you see that $user
variable in the closure
’s first param? It’s passed to all the defined gates and policies by default. It’s the currently logged-in user.
This way, you can define as many gates as you want. A few samples below.
Gate::define('edit-post', function ($user, $post) {
return $user->id == $post->user_id;
});
Gate::define('delete-post', function ($user, $post) {
return $user->id == $post->user_id;
});// Gate::allows('delete-post', Post::find(10));
// Gate::allows('edit-post', $post)
Whenever you need the second or extra parameter in your closure, you’ll need to pass these from wherever you’re calling. You can pass the extra parameters in two ways. If you need only one parameter next to $user variable, then you can pass it as in the Gate::allows
example. But if you require more than one parameter in your closure, then you’ll need to use an array as the second params like the following.
Gate::allows('gate-name', [$param1, $param2]);
In this way, these values in your array will be passed as the parameters in sequence after $user variable to your closure.
Is allows is the only method to authorize?
No, there are several methods.
- allows — Checks if the given ability passes.
- denies — Negates the
allows
conditions. - check — Checks if a single or array of abilities are allowed.
- any — Checks if any of single or array of abilities passes.
- none — Negates the
any
conditions. - authorize — Checks if the ability is allowed or throws an
Illuminate\Auth\Access\AuthorizationException
exception.
// use Illuminate\Auth\Access\ResponseGate::allows(string $ability, $arguments = []): bool
Gate::denies(string $ability, $arguments = []): bool
Gate::check(array|string $abilities, $arguments = []): bool
Gate::any(array|string $abilities, $arguments = []): bool
Gate::none(array|string $abilities, $arguments = []): bool
Gate::authorize(string $ability, $arguments = []): Response
Anything more?
Suppose, you want to check an authorization against a specific user not just the currently logged-in user, then you can use forUser($userId)
.
Gate::forUser(User::find(10))->allows('edit-post', Post::find(20));
The above code will then check if the user id 10 is allowed to edit the post with the id of 20 or not.
Bear in mind
If you somewhere come face to a situation when you’ll need to approve all the authorization for a user or a user will have a privilege to specific ability, then you can use the before
method of Gates. You can add as many before
callbacks as you want. But if any of your before callback returns a non-null value, it will be returned as your authorization logic and your referenced authorization gate names will not be used. Like, before
you can add multiple after
callbacks too. But, if your authorization before
or custom doesn’t return any value, then the after will overwrite that value.
Gate::before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});Gate::after(function ($user, $ability, $result, $arguments) {
if ($user->isSuperAdmin()) {
return true;
}
});
That’s all about the gates. You can dig in by going to the concrete Gate definition class.
Previously we defined the gates as
Gate::define('ability', function (User $user){});
But we can use classes as we do for the controllers in web.php
or api.php
. To do so we can define gates as follows.
<?phpnamespace App;class CustomPolicy
{
public function editPost (User $user, Post $post) {
return $user->id === $post->user_id;
}
}
Now, in your app/Providers/AuthServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
public function boot () {
// to keep the code in one-line, used App namespace.
// closure is replaced with ClassName@methodName format.
Gate::define('edit-post', 'App\CustomPolicy@editPost');
}
}
This way your provider stays clean. And, you can achieve the same for what we did in our previous examples.
You can find the second part of this article HERE.
Happy coding ❤