minaremonshaker's avatar

Does Gate::before bypass a policy if the callback returns true for a given permission?

I’m defining a global Gate::before check in my AppServiceProvider like this:

Gate::before(function (User $user, $ability) {
    if ($user->hasAnyPermission(['view_any_ticket'])) {
        return true;
    }
    return null;
});

In my TicketPolicy, the view method looks like this:

public function view(User $user, Ticket $ticket): bool
{
    if ($user->hasPermissionTo('view_ticket')) {
        return $user->id === $ticket->user_id;
    }
    return false;
}

Then, in my controller, I authorize like this:

Gate::authorize('view', $ticket);

If a user has the view_any_ticket permission through a “moderator” role, will the global Gate::before return true and completely bypass the TicketPolicy::view() method?

0 likes
5 replies
LaryAI's avatar
Level 58

Yes, the Gate::before callback will bypass your TicketPolicy::view() method if it returns a non-null value.

Here's how it works:

  • When an ability is being checked (e.g. Gate::authorize('view', $ticket);), Laravel will first run any Gate::before callbacks.
  • If your Gate::before returns a non-null value (true or false), that value is immediately considered the final answer for the authorization check, and no further policy methods will be called for that ability check.
  • If your Gate::before returns null, Laravel falls through to the associated policy method (like view) and evaluates that as normal.

So, with your code:

Gate::before(function (User $user, $ability) {
    if ($user->hasAnyPermission(['view_any_ticket'])) {
        return true;
    }
    return null;
});

If the user has the view_any_ticket permission, Gate::before returns true, and Laravel grants access for any ability (including 'view'), without ever calling your policy's view method.

To sum up:
If Gate::before returns true, the TicketPolicy::view() method is not called and access is granted immediately.

Reference:
The official Laravel documentation on Gates says:

"If the before callback returns a non-null result that result will be considered the result of the authorization check."

So in your scenario, yes, the global Gate::before will bypass the policy method if it returns true.

1 like
tykus's avatar
tykus
Best Answer
Level 104

Correct, you are intercepting gate checks which typically allows super users or other elevated admins to perform any gated action.

However, The example you are providing seems too specific to one resource (tickets). Be aware that this before check will be executed and evaluated before all authorization checks, even for non-ticket related resources!

IMHO, it would be more appropriate to remove the before Gate check and instead implement that check using a Policy Filter in the TicketPolicy class, to ensure that interception is scoped only to the Ticket model

public function before(User $user, string $ability): bool|null
{
    if ($user->hasAnyPermission(['view_any_ticket'])) {
        return true;
    }
 
    return null;
}
1 like
minaremonshaker's avatar

Hi @tykus,

Thanks for your reply. In this case, will the before callback run by default, or do I need to explicitly call it via Gate::authorize in the controller?

 Gate::athorize("before" , Tickit::class)
tykus's avatar

No. Read the docs I shared above

The before method will be executed before any other methods on the policy, giving you an opportunity to authorize the action before the intended policy method is actually called

Glukinho's avatar

Yes, and it is the purpose of Gate::before: if it returns true or false it is applied as authorization result and no need to see the policy or anything else.

If null is returned (or nothing is returned which is the same as null) it is treated as "don't know" and Laravel looks in a policy to get determined true/false result.

Please or to participate in this conversation.