In a Laravel app policies are a great way to organize authorization logic that revolves around models.

For the longest time, I've been using Gate::before to allow superadmins to do anything they want. While working on a new app, it finally clicked how Gate::after can be useful too. I'd like to share that knowledge in this blog post.

This post assumes that you understand and have experience with Laravel's authorization features. I've not read the relevant documentation first.

Explaining Gate::before #

The new app our team is building that was mentioned in the intro, is called attended.io. When finished, it will allow event organizers to register their events, slots, and speakers. Attendees can leave feedback on the slots of the events and its speakers.

Let's take a look at a part of the policy that handles whether or not a user is allowed to administer an event.

class EventPolicy
{
   use HandlesAuthorization;

   public function administer(User $user, Event $event)
   {
      return $user->organizes($event);
   }
}

With this policy you can make the auth check like this:

$user->can('administer', $event); // returns a boolean

This will return true for users that organize the event, and false for all others.

In our system, we also have superadmins that are allowed to do anything. They do not organize the event, but they still should be allowed to administer it. This can be solved by adding a Gate::before:

// somewhere in a service provider

Gate::before(function ($user, $ability) {
    if ($user->isSuperAdmin()) {
        return true;
    }
});

The closure passed to Gate::before will be used before the EventPolicy (and all other policies) get called. If you're a superadmin you can now administer the event even though you're not organizing that event. Life is good!

Notice that if you're not a superadmin we don't return false but null (by not returning anything). This is done on purpose. If we were to return false here, the policies classes wouldn't be checked. Only superadmins would be able to administer events.

Explaining Gate::after #

In our attended.io app, a slot can be reviewed by users that are logged in. A slot can only be reviewed once per user. A user may not review a slot before the slot has started.

Here's the policy class:

class SlotPolicy
{
    use HandlesAuthorization;

    public function review(User $user, Slot $slot)
    {
        if ($user->hasReviewed($slot)) {
            return false;
        }

        if ($slot->starts_at->isFuture()) {
            return false;
        }
        
        return true;
    }
}

For non-superadmins this works fine. All our rules are respected. But the Gate::before - mentioned in the previous section - grants our superadmins way too much power. Superadmins can now review slots multiple times, and they can review a slot before it starts. Let's fix that!

Instead of using Gate::before, let's use Gate::after.

// somewhere in a service provider

Gate::after(function ($user, $ability) {
   return $user->isSuperAdmin();
});

With this Gate::after in place instead of Gate::before the policies will get called first, even for superadmins. All users, including superadmins, are now unable to review slots that haven't started yet. Great!

If you have a keen eye, you probably noticed that for the review check on SlotPolicy the Gate::after doesn't get called because SlotPolicy already returns true. And that's true, for that particular check will work without the Gate::after.

But let's take a look again at the EventPolicy from the previous section.

class EventPolicy
{
   use HandlesAuthorization;

   public function administer(User $user, Event $event)
   {
      return $user->organizes($event);
   }
}

As it stands, superadmins are not allowed to administer events because administer returns a boolean. Let's fix that.

class EventPolicy
{
  use HandlesAuthorization;

  public function administer(User $user, Event $event)
  {
     if($user->organizes($event)) {
        return true;
     }
  }
}

Now we don't return anything for regular users that don't organize the event. In this case our Gate::after callback will get called and it will return true for superadmins, false for all other users.