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 refusalA 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=PostEach 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>
@endcannotIn 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.