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

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.

Webmentions

ArielSalvadorDev retweeted on 30th August 2019
ArielSalvadorDev liked on 30th August 2019
Mozammil liked on 30th August 2019
Mark Myers liked on 30th August 2019
이현석 Hyunseok Lee liked on 30th August 2019
Miguel Piedrafita @ LaraconEU liked on 29th August 2019
Mithicher Baro retweeted on 29th August 2019
Mithicher Baro liked on 29th August 2019
Wyatt liked on 29th August 2019
abhishek retweeted on 29th August 2019
rik ? liked on 29th August 2019
Muhammad Sumon Molla Selim liked on 29th August 2019
Niels liked on 29th August 2019
Flychat liked on 29th August 2019
Francesco Lettera liked on 29th August 2019
Ryan Rosiek liked on 29th August 2019
Matthew Poulter liked on 29th August 2019
Arputharaj liked on 29th August 2019
Arputharaj retweeted on 29th August 2019
George From Ohio liked on 29th August 2019
Vishwanath liked on 29th August 2019
Chris Brown liked on 29th August 2019
Mike liked on 29th August 2019
Miguel Stevens liked on 29th August 2019
Peter Brinck ? liked on 29th August 2019
. liked on 29th August 2019
oluwajubelo loves VueJS ? liked on 29th August 2019
oluwajubelo loves VueJS ? retweeted on 29th August 2019
Katerina Trajchevska liked on 29th August 2019
Sven liked on 29th August 2019
David Lartey ?? ?? liked on 29th August 2019
Roman Pronskiy liked on 17th August 2019
Gianmarco Nalin liked on 13th August 2019
ArielSalvadorDev liked on 13th August 2019
ダビッド トレス liked on 13th August 2019
ArielSalvadorDev retweeted on 13th August 2019
ダビッド トレス retweeted on 13th August 2019
Beau Simensen replied on 13th August 2019
My understanding of ::after was that it worked just like ::before. It would allow me to check to see if a user was denied access and override it and log it at the same time if they were a super admin (an "employee" in my case).
Beau Simensen replied on 13th August 2019
I'd read that, actually. :) I think ::after likely works differently than I expected. :-/