Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Authorization

Authentication answers "who is this?", authorization answers "what may they do?". A logged-in user must not edit other people's posts. Laravel offers two tools for such rules: simple gates and policy classes.

Gates

A gate is a named rule. It is declared in the boot() method of the AppServiceProvider class:

use App\Models\User;
use Illuminate\Support\Facades\Gate;
 
public function boot(): void
{
    Gate::define('access-admin', function (User $user) {
        return $user->is_admin;
    });
}

Checking in code:

if (Gate::allows('access-admin')) {
    // show the admin panel
}
 
Gate::authorize('access-admin'); // a 403 error on refusal

A gate suits rules that are not tied to a particular model.

Policies

Rules that concern a model are gathered in a policy class:

php artisan make:policy PostPolicy --model=Post

Each method is responsible for one action and receives the user and the model:

namespace App\Policies;
 
use App\Models\Post;
use App\Models\User;
 
class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
 
    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->is_admin;
    }
 
    public function create(User $user): bool
    {
        return $user->hasVerifiedEmail();
    }
}

Laravel finds the policy by the model name: Post — PostPolicy.

Checking in a controller

use Illuminate\Support\Facades\Gate;
 
public function update(Request $request, Post $post)
{
    Gate::authorize('update', $post);
 
    $post->update($request->validated());
 
    return redirect()->route('posts.show', $post);
}

If the rule returned false, execution stops with a 403 error. For actions without a model pass the class: Gate::authorize('create', Post::class).

Checking through the user

if ($request->user()->can('update', $post)) {
    // ...
}
 
if ($request->user()->cannot('delete', $post)) {
    abort(403);
}

In a template

The @can directive shows a block only to those allowed the action:

@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">Edit</a>
@endcan
 
@cannot('delete', $post)
    <p>Only the author can delete the post.</p>
@endcannot

In a route

Route::put('/posts/{post}', [PostController::class, 'update'])
    ->middleware('can:update,post');

An administrator can do anything

The before() method runs ahead of the other rules of a policy:

public function before(User $user): ?bool
{
    return $user->is_admin ? true : null;
}

null means "decide with the regular rule".

Common mistake

A button hidden in a template is not protection. The address can be typed by hand or the request sent bypassing the interface. Permissions must be checked on the server: in the controller, the request class or the route. The @can directive is only for a tidy interface.

Thanks for reading!

Donate to the site — we work hard for you.