Laravel Policy - first try and problem solve

Backend Apr 24, 2021

Basics

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

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

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

Gate

The code above can be written using 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 controller like this

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

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

Policy

php artisan make:policy PostPolicy --model=Post

After generated 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 controller by controller method or via 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 then default

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

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.