twitter rss github stackoverflow
Joseph Silber's Avatar

Joseph Silber

Gate and authorization improvements in Laravel 5.3

After looking at the improvements coming to authentication, let's now take a look at what's in store for authorization in Laravel 5.3.

Note: some of the features discussed below have originaly been introduced in the middle of the 5.2 cycle. However, all of these features have been tweaked and refined for 5.3, so we'll cover them here.

Laravel authorization primer

If you're already familiar with authorization in Laravel, you can skip right ahead.

Laravel's authorization is built around 2 main concepts:

  1. A Gate class. The gate is the official authority on who has what ability (i.e. who can do what). You use the gate to register a user's abilities, and later check the gate if the user can perform a given action.

  2. Policy classes. Policies are responsible for checking abilities on a single model type. For each model class in your system you want to authorize against, you'll have a matching policy class.

To see how all this works together, let's generate a policy class for a theoretical Task model, by running php artisan make:policy TaskPolicy. Once created, we'll add a simple update method that checks if the user can update the given task:

namespace App\Policies;

use App\Task;
use App\User;

class TaskPolicy
{
    public function update(User $user, Task $task)
    {
        return $user->id === $task->user_id;
    }
}

Next we should register this policy with the gate, by adding it to the AuthServiceProvider's policy mappings:

protected $policies = [
    \App\Task::class => \App\Policies\TaskPolicy::class,
];

Behind the scenes, this will register the policy with the Gate. From now on, any authorization check against a Task will be routed to the TaskPolicy.

To demonstare, let's create a simple route to update a given task:

Route::put('tasks/{task}', function (App\Task $task) {
   abort_unless(Gate::allows('update', $task), 403);

   $task->update(request()->input());
});

We check the gate if the user can update the given task. The gate will call the update method on our policy, passing it both the currently authenticated user as well as the given task (if the user is not logged in, the gate will automatically deny all ability inquiries). If the ability is not granted, we abort with a 403.

(By the way, don't you just love that abort_unless function? I mean, just look at that! It reads like a full sentence: "abort unless the gate allows updating the task". This is Laravel at its best.)

Easily authorizing requests in controllers

Controllers in Laravel use the AuthorizesRequests trait to easily authorize any given request. You can call the authorize method from within a controller and it'll check for the ability at the gate. If the ability is denied, the request will automatically be aborted with a 403 response.

To see this in action, let's move our route to a controller:

use App\Task;

class TaskController
{
    public function update(Task $task)
    {
        $this->authorize('update', $task);

        $task->update(request()->input());
    }
}

Just like before, the gate will consult our TaskPolicy's update method and return its result.

Automatic ability names

Laravel actually goes a tiny step further, and allows you to omit the ability name when calling the authorize method. When the ability name is not provided, Laravel will automatically pick up the ability name from the controller's method.

In our sample controller, calling $this->authorize($task) would still end up consulting TaskPolicy@update for the check. Pretty nifty!

In Laravel 5.2, the automatic ability name is always the name of the controller method that calls it. Given the following controller:

use App\Task;

class TaskController
{
    public function show(Task $task)
    {
        $this->authorize($task);

        return view('tasks.show')->with('task', $task);
    }

    public function create()
    {
        $this->authorize(Task::class);

        return view('tasks.create');
    }
}

...you would have a policy with methods whose names match up with the names of the controller's methods:

use App\Task;
use App\User;

class TaskPolicy
{
    public function show(User $user, Task $task)
    {
        // check if the user can view the given $task
    }

    public function create(User $user)
    {
        // check if the user can create new tasks
    }
}

The problem with matching ability names to method names

Update August 8, 2016: after publishing this article, Taylor made some additional changes to authorization. The following has been updated to reflect these changes.

On the surface this seems quite intuitive, but if you looks closely you'll find some cracks in this convention.

Consider these 2 samples of where this convention breaks down:

  1. Checking abilities outside of a controller. The controller's method name is not always the most intuitive way to check an ability. Let's examine this template, where we loop through and check each task:

    @foreach ($tasks as $task)
        @if ($user->can('show', $task))
            <a href="{{ url('tasks', $task) }}">View task</a>
        @endif
    @endforeach

    Hmmm... That doesn't look quite right. What does $user->can('show', $task) check for? Does it check if the user can show tasks? What does that even mean? We want to know if they can view it, not show it!?

  2. Duplicate policy methods. The check for whether we should show the form to create a new task is the very same check that determines whether we allow the user to subsequently store that task. Naively mapping the controller's method names leads to the policy having two identical methods:

    use App\User;
    
    class TaskPolicy
    {
        public function create(User $user)
        {
            // check if the user can create new tasks
        }
    
        public function store(User $user)
        {
            // check if the user can create new tasks
        }
    }

    These two run the very same check, but we need them both for the two different methods on the controller. The same duplication applies to edit and update.

Normalizing ability names

In Laravel 5.3, the automatic ability names are getting an upgrade. Instead of blindly using the method name, Laravel will pick a sensible ability name based on the action you're trying to complete. If you're trying to view an item, it'll use the view ability name. If you're trying to create a new record, it'll use the create ability name, even if you call it from the controller's store method.

Laravel does this via a simple mapping between the methods and abilities. Here's the complete map straight from the source:

[
    'show' => 'view',
    'create' => 'create',
    'store' => 'create',
    'edit' => 'update',
    'update' => 'update',
    'destroy' => 'delete',
]

This happens automatically, with no change to the code in your controller. You can now check if $user->can('view', $task) in your views, which is much more intuitive.

Now that the controller doesn't always call the gate with ability names matching the controller methods, the policies should no longer have those methods. Given this controller:

use App\Task;

class TaskController
{
    public function create()
    {
        $this->authorize(Task::class);

        return view('tasks.create');
    }

    public function store()
    {
        $this->authorize(Task::class);

        Task::create(request()->input());
    }
}

...you would have a policy with just a create method:

use App\User;

class TaskPolicy
{
    public function create(User $user)
    {
        // check if the user can create tasks
    }
}

This create method wil be called from both create and store in the controller. Much cleaner!

Full policy generator

To help out with these changes, the policy generator is getting a nice boost. If you pass a model name to the generator it'll fully stub out the necessary policy methods to cover all default resource controller methods:

php artisan make:policy TaskPolicy --model=Task

You can see the raw stub in Laravel's source code, which should give you a better picture about the methods being generated.

The can middleware

While authorizing abilities inline works well, sometimes it makes more sense to do it at the route level - such as when the same ability applies to a whole group of routes. Laravel ships with a can middleware just for this purpose.

Let's define a general ability named access-dashboard:

Gate::define('access-dashboard', function ($user) {
    return $user->isAdmin();
});

To guard a whole group of routes against unauthorized access, we can wrap them in a group with the can middleware:

Route::group(['middleware' => 'can:access-dashboard'], function () {
    // define all dashboard routes...
});

Now the gate will be checked for the access-dashboard ability before running any routes in this group.

You can also pass a second argument to the middleware. This can be either the model's class name, or - to check for a given model instance - the model's route parameter name:

Route::get('tasks/{task}', [
   'uses' => 'TaskController@show',
   'middleware' => 'can:view,task',
]);

Route::post('tasks', [
   'uses' => 'TaskController@store',
   'middleware' => 'can:create,App\Task',
]);

When passing a route parameter name, the middleware will pull the model instance from the route itself. Refer to Laravel's documentation for more on route model binding.

Authorizing a full resource controller

Another neat gem in the authorization system is the authorizeResource method now available in controllers. This has the potential to considerably clean up your controllers. So what does this magical authorizeResource do?

In short, it replaces all your calls to authorize in the various controller methods with a single call in the constructor:

use App\Task;

class TaskController
{
    public function __construct()
    {
        $this->authorizeResource(Task::class);
    }

    // ... all resource methods ...
}

That's all it takes! You can now skip all calls to authorize in the individual methods. Laravel will now automagically check the right abilities for any given action. It's pretty magical actually.

And... that's it.

There are many more subtle improvements to authorization than what we've covered, but we have to stop somewhere. If you're brave enough, take a peek at the source code and you're sure to find some gems of your own.


Questions? Comments? Complaints? Ping me on Twitter.

Recent posts

  1. Getting the current user (or other session data) in a Laravel controller constructor

    Session data is not available directly in controller constructors. Let's see how we can work around that.

    Read more ➺

  2. The new Closure::fromCallable() in PHP 7.1

    Let's take a look at a neat little feature coming in PHP 7.1: easily converting any callable into a proper Closure using the new Closure::fromCallable() method.

    Read more ➺

  3. Improvements to authentication in Laravel 5.3

    Authentication has gotten some nice improvements in 5.3, so let's examine it piece by piece. We'll start with the current state of affairs, then take it from there.

    Read more ➺