Skip to content

Authorization (Phân quyền)

Giới thiệu (Introduction)

Ngoài authentication, Laravel cũng cung cấp cách đơn giản để phân quyền (authorize) user actions trên resources. Ví dụ: dù user đã authenticated, họ có thể không được phép update hay delete certain Eloquent models.

Laravel cung cấp 2 cách chính:

Phương phápKhi nào dùng
GatesClosure-based, đơn giản — cho actions không liên quan đến model (ví dụ: xem admin dashboard)
PoliciesClass-based — cho actions liên quan đến model/resource cụ thể

Bạn có thể kết hợp cả hai trong ứng dụng.

Gates

Viết Gates

Định nghĩa trong AppServiceProvider method boot:

php
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;

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

Authorize Actions qua Gates

php
use Illuminate\Support\Facades\Gate;

// Kiểm tra
if (Gate::allows('update-post', $post)) {
    // User có thể update...
}

if (Gate::denies('update-post', $post)) {
    // User không được phép...
}

// Authorize (throw exception nếu denied)
Gate::authorize('update-post', $post);

// Kiểm tra nhiều abilities
if (Gate::any(['update-post', 'delete-post'], $post)) {
    // User có thể update HOẶC delete...
}

if (Gate::none(['update-post', 'delete-post'], $post)) {
    // User KHÔNG thể update NÀ delete...
}

Gate Responses

Trả về response chi tiết thay vì boolean:

php
use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('edit-settings', function (User $user) {
    return $user->isAdmin
        ? Response::allow()
        : Response::deny('Bạn phải là quản trị viên.');
});

Intercepting Gate Checks

php
Gate::before(function (User $user, string $ability) {
    if ($user->isAdmin()) {
        return true; // Super admin bypass mọi kiểm tra
    }
});

Gate::after(function (User $user, string $ability, bool|null $result) {
    // Chạy sau mỗi authorization check...
});

Inline Authorization

php
use Illuminate\Support\Facades\Gate;

Gate::allowIf(fn (User $user) => $user->isAdmin());
Gate::denyIf(fn (User $user) => $user->banned());

Policies

Tạo Policies

bash
php artisan make:policy PostPolicy --model=Post

Đăng ký Policies

Laravel tự động phát hiện policies theo convention {Model}Policy. Đăng ký thủ công (nếu cần):

php
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;

Gate::policy(Post::class, PostPolicy::class);

Viết Policy Methods

php
<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    /**
     * User có thể xem post?
     */
    public function view(User $user, Post $post): bool
    {
        return true;
    }

    /**
     * User có thể tạo posts?
     */
    public function create(User $user): bool
    {
        return true;
    }

    /**
     * User có thể update post?
     */
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }

    /**
     * User có thể xóa post?
     */
    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
}

Policy Responses

php
use Illuminate\Auth\Access\Response;

public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
        ? Response::allow()
        : Response::deny('Bạn không sở hữu bài viết này.');
}

Methods Without Models

Một số actions không liên quan đến model instance:

php
public function create(User $user): bool
{
    return $user->role === 'writer';
}

Guest Users

Mặc định gates và policies trả về false cho guest. Type-hint nullable:

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

Policy Filters

php
public function before(User $user, string $ability): bool|null
{
    if ($user->isAdmin()) {
        return true; // Admin bypass tất cả
    }

    return null; // Tiếp tục kiểm tra policy method
}

Authorize Actions qua Policies

Qua User Model

php
if ($request->user()->can('update', $post)) {
    // ...
}

if ($request->user()->cannot('update', $post)) {
    // ...
}

// Actions không cần model instance
if ($request->user()->can('create', Post::class)) {
    // ...
}

Qua Gate Facade

php
use Illuminate\Support\Facades\Gate;

Gate::authorize('update', $post);

Qua Controller Helper

php
public function update(Request $request, Post $post)
{
    $this->authorize('update', $post);

    // User được phép update...
}

Qua Middleware

php
Route::put('/post/{post}', function (Post $post) {
    // ...
})->middleware('can:update,post');

// Actions không cần model
Route::post('/post', function () {
    // ...
})->middleware('can:create,App\Models\Post');

Qua Blade Templates

blade
@can('update', $post)
    <!-- User có thể update -->
@elsecan('create', App\Models\Post::class)
    <!-- User có thể tạo -->
@else
    <!-- Không có quyền -->
@endcan

@cannot('update', $post)
    <!-- User không thể update -->
@endcannot

@canany(['update', 'view', 'delete'], $post)
    <!-- User có ít nhất một quyền -->
@endcanany

Authorization & Inertia

Truyền authorization data vào Inertia responses:

php
use App\Models\Post;

return Inertia::render('Posts/Index', [
    'posts' => Post::all()->map(function (Post $post) {
        return [
            'id' => $post->id,
            'title' => $post->title,
            'can' => [
                'update' => $request->user()->can('update', $post),
                'delete' => $request->user()->can('delete', $post),
            ],
        ];
    }),
]);