Laravel Policy - first try and problem solve

Backend Apr 24, 2021

Basics

It is not uncommon to see code like this when related to user authorization. All the code was mixed in a single controller. Not only does this add complexity to the controller but also hard to maintain. The only advantage of this is to finish work earlier, but regret it in the future.

public function update(Request $request, Post $post)
{
    if ($request->user->id !== $post->user_id) {
        abort(403)
    }
}
authorize request in the controller

Therefore, Laravel introduces gate and policy. While the gate is rather a simple implementation to address the authorization problem, the Laravel policy makes authorization more complete.

Gate

The code above can be written using the gate. In the boot() method of AuthServiceProvider, the gate was defined in this way.

Gate::define('update-post', function ($user, $post) {
    return $user->id === $post->user_id;
});

It can be used in controllers like this

if (Gate::allows('update-post', $post)) {
    // The current user can update the post...
}

However, this can be complex since all the gates were defined by the closure. It is necessary to use a more sophisticated way. Then the policy was introduced.

Policy

php artisan make:policy PostPolicy --model=Post

After generating the policy, we can define the access rules in App\Policies\PostPolicy.

public function update(?User $user, Post $post)
{
    return optional($user)->id === $post->user_id;
}

The policy can be used in the controller by controller methods or via the user model.

public function update(Request $request, Post $post)
{
    // controller method
    $this->authorize('update-post', $post)
    
    // via user model
    if (!$user->can('update', $post)) {
        abort(403)
    }
}

More usage can be seen in the Laravel documents.

Policy auto-discovery not working

The policy auto-discovery could not function well if the namespace structure was changed for models. The problem can be solved by adding a custom resolver in the boot() method of AuthServiceProvider.

Gate::guessPolicyNamesUsing(function ($modelClass) {
    return 'App\\Policies\\' . class_basename($modelClass) . 'Policy';
});

Using guard other than the default one

authorizeForUser() can be used in this scenario by specifying the guard to get the user.

$this->authorizeForUser($request->user('api'), 'update', $post);

References

[1]: Authorization - Laravel - The PHP Framework For Web Artisans

[2]: How to make Policy Auto-Discovery new Laravel feature work with my models in a different folder? - Stack Overflow

[3]: Laravel only allow owner user to access route - Stack Overflow

Tags