Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

When to use Gate::after in Laravel

Original – by Freek Van der Herten – 4 minute read

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. If 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.

Stay up to date with all things Laravel, PHP, and JavaScript.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

Patrick Kellar avatar

The only issue I seem to run into with this is when using blade directives like @can('withdraw', $post) to render buttons when items are at different lifecycle stages. This causes all of the action buttons to be visible to admin users, whether the action should be avaiable or not. Any suggestions on a better way to do that?

Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.