> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 授權（Gate 與 Policy）

> 解說如何使用 Laravel 的 Gate 與 Policy 管理使用者的動作權限。

## 認證與授權的差異

**認證（Authentication）** 用來確認使用者「是誰」，登入處理就是代表性例子。
**授權（Authorization）** 用來判斷該使用者「能做什麼」。例如：只能編輯自己的貼文、只有管理員能修改設定等控制。

在[認證](./authentication)實作完登入功能後，下一步就是授權。

<Info>
  Laravel 的授權功能提供 Gates 與 Policies 兩種方式。使用哪一種取決於實際情境。
</Info>

**選用的判斷基準**

| 情境                | 建議     |
| ----------------- | ------ |
| 不綁定特定模型的簡易判斷      | Gate   |
| 針對模型的 CRUD 操作權限管理 | Policy |
| 管理員儀表板的存取控制       | Gate   |
| 部落格文章的建立、編輯、刪除權限  | Policy |

## Gate

Gate 是以 Closure 為基礎、簡單的授權檢查。適合用在不綁定特定模型的權限判定。

### 透過 Gate 的授權流程

```mermaid theme={null}
flowchart TD
    A["AppServiceProvider::boot()<br>(App\\Providers\\AppServiceProvider)"] --> B["Gate::define('ability', fn)<br>註冊授權邏輯"]
    C["從 Controller 進行授權檢查"] --> D{"是否設定了<br>Gate::before()?"}
    D -->|"是"| E{"before() 的回傳值"}
    E -->|"true / false"| F["以 before() 的結果作為最終判定"]
    E -->|"null"| G["執行 define() 的 Closure"]
    D -->|"否"| G
    G --> H{"Closure 的回傳值"}
    H -->|"true"| I["✓ 允許存取"]
    H -->|"false"| J["✗ 拒絕存取"]
    F -->|"true"| I
    F -->|"false"| J
    J -->|"Gate::allows()"| K["回傳 false<br>→ 手動呼叫 abort(403)"]
    J -->|"Gate::authorize()"| L["AuthorizationException<br>→ 自動 403 Forbidden"]
```

### 定義 Gate

Gate 於 `App\Providers\AppServiceProvider` 的 `boot` 方法中使用 `Gate::define()` 定義。

```php theme={null}
// app/Providers/AppServiceProvider.php

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

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

Gate 的 Closure 第 1 個參數固定為目前已認證的使用者。第 2 個之後的參數可以傳入對應的模型等額外資訊。

### 用 Gate 檢查權限

在 Controller 中使用 Gate 檢查權限，可使用 `Gate::allows()` 或 `Gate::denies()`。

```php theme={null}
use Illuminate\Support\Facades\Gate;

public function update(Request $request, Post $post): RedirectResponse
{
    if (! Gate::allows('update-post', $post)) {
        abort(403);
    }

    // 更新貼文的處理...

    return redirect('/posts');
}
```

### 用 Gate::authorize() 拋出例外

若希望在無權限時自動回傳 403，可使用 `Gate::authorize()`，可省下寫 `abort(403)` 的功夫。

```php theme={null}
public function update(Request $request, Post $post): RedirectResponse
{
    Gate::authorize('update-post', $post);

    // 有權限才會抵達此處
    // 更新貼文的處理...

    return redirect('/posts');
}
```

<Tip>
  `Gate::authorize()` 於無權限時會拋出 `Illuminate\Auth\Access\AuthorizationException`。Laravel 會自動將其轉為 403 HTTP 回應。
</Tip>

### 管理員繞過（before 方法）

若想賦予管理員所有權限，可使用 `Gate::before()`。

```php theme={null}
use App\Models\User;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    // $ability 會是即將執行的 Gate 名稱（例如 'update-post'）
    Gate::before(function (User $user, string $ability) {
        if ($user->isAdministrator()) {
            return true;
        }
    });

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

當 `before` Closure 回傳非 `null` 的值時，該結果即為最終權限判定。若回傳 `null` 或不回傳任何值，則進入一般的 Gate 判定。

### Blade 模板中的 @can / @cannot

在模板中使用 Gate 判定時，可用 `@can` 與 `@cannot` 指令。

```blade theme={null}
@can('update-post', $post)
    <a href="{{ route('posts.edit', $post) }}">編輯</a>
@endcan

@cannot('update-post', $post)
    <p>你沒有編輯此貼文的權限。</p>
@endcannot
```

## Policy

Policy 是把與特定模型相關的授權邏輯整理成類別。對 `Post` 模型的建立、瀏覽、編輯、刪除權限管理，Policy 比 Gate 更合適。

### 透過 Policy 的授權流程

```mermaid theme={null}
flowchart TD
    A["HTTP 請求"] --> B["Controller"]
    B --> C["Gate::authorize('update', $post)"]
    C --> D{"是否定義了<br>PostPolicy::before()?"}
    D -->|"是"| E{"before() 的回傳值"}
    E -->|"true / false"| F["以 before() 的結果作為最終判定"]
    E -->|"null"| G["執行 PostPolicy::update(User, Post)"]
    D -->|"否"| G
    G --> H{"$user->id === $post->user_id?"}
    H -->|"true"| I["✓ 繼續處理"]
    H -->|"false"| J["✗ AuthorizationException<br>→ 403 Forbidden"]
    F -->|"true"| I
    F -->|"false"| J
```

### 產生 Policy

使用 `make:policy` Artisan 指令產生 Policy 類別。

```shell theme={null}
php artisan make:policy PostPolicy
```

若要產生包含對應模型所有 CRUD 方法的骨架，使用 `--model` 選項。

```shell theme={null}
php artisan make:policy PostPolicy --model=Post
```

會產生 `app/Policies/PostPolicy.php`。

### 模型與 Policy 的自動探索

Laravel 預設會依命名慣例自動發現 Policy。

* 模型：`app/Models/Post.php`
* Policy：`app/Policies/PostPolicy.php`

遵循此命名慣例的話，就不需要註冊 Policy。

<Info>
  若不遵循命名慣例或想手動註冊，可在 `AppServiceProvider` 的 `boot` 方法中使用 `Gate::policy()`。

  ```php theme={null}
  use App\Models\Post;
  use App\Policies\PostPolicy;
  use Illuminate\Support\Facades\Gate;

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

  在 Laravel 13 中，也可在模型上加上 `#[UsePolicy]` 屬性以宣告式方式註冊。

  ```php theme={null}
  use App\Policies\PostPolicy;
  use Illuminate\Database\Eloquent\Attributes\UsePolicy;

  #[UsePolicy(PostPolicy::class)]
  class Post extends Model {}
  ```
</Info>

### 實作 Policy 方法

使用 `--model` 選項產生的 Policy 已包含對應標準 CRUD 動作的方法。

```php theme={null}
<?php

namespace App\Policies;

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

class PostPolicy
{
    /**
     * 是否可瀏覽貼文列表
     */
    public function viewAny(User $user): bool
    {
        return true; // 已認證使用者皆可瀏覽
    }

    /**
     * 是否可瀏覽特定貼文
     */
    public function view(User $user, Post $post): bool
    {
        return true; // 已認證使用者皆可瀏覽
    }

    /**
     * 是否可建立貼文
     */
    public function create(User $user): bool
    {
        return true; // 已認證使用者皆可發文
    }

    /**
     * 是否可更新貼文
     */
    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; // 僅可刪除自己的貼文
    }
}
```

### 管理員繞過（before 方法）

在 Policy 內也可定義 `before` 方法，賦予管理員所有權限。

```php theme={null}
/**
 * 會先於其他 Policy 方法執行的預先檢查
 * $ability 會是 Policy 方法名稱（如 'update'、'delete'）
 */
public function before(User $user, string $ability): bool|null
{
    if ($user->isAdministrator()) {
        return true; // 管理員允許所有操作
    }

    return null; // 回傳 null 時進入一般的 Policy 方法
}
```

<Warning>
  `before` 方法只有在 Policy 類別上存在對應方法時才會被呼叫。例如若不存在 `update` 方法，`before` 也不會被呼叫。
</Warning>

### before Callback 的執行順序

```mermaid theme={null}
flowchart TD
    A["開始授權檢查<br>authorize('update', $post)"] --> B["執行 PostPolicy::before()"]
    B --> C{"回傳值是否為 null?"}
    C -->|"非 null（true / false）"| D["以 before() 的結果作為最終判定"]
    C -->|"null"| E["執行一般 Policy 方法<br>update() / delete() 等"]
    E --> F{"Policy 方法回傳值"}
    F -->|"true"| G["✓ 允許存取"]
    F -->|"false"| H["✗ 拒絕存取 → 403"]
    D -->|"true（管理員繞過）"| G
    D -->|"false"| H
```

## 在 Controller 中使用 Policy

### authorize() 方法

Laravel 的 Controller 具備 `authorize()` 輔助方法（若不使用 base Controller，則改用 `Gate::authorize()`）。

<Tabs>
  <Tab title="Gate::authorize()">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

    use App\Models\Post;
    use Illuminate\Http\RedirectResponse;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Gate;

    class PostController extends Controller
    {
        public function update(Request $request, Post $post): RedirectResponse
        {
            Gate::authorize('update', $post);

            // 更新貼文的處理...

            return redirect()->route('posts.index');
        }

        public function store(Request $request): RedirectResponse
        {
            Gate::authorize('create', Post::class);

            // 建立貼文的處理...

            return redirect()->route('posts.index');
        }

        public function destroy(Post $post): RedirectResponse
        {
            Gate::authorize('delete', $post);

            $post->delete();

            return redirect()->route('posts.index');
        }
    }
    ```
  </Tab>

  <Tab title="User::can() / cannot()">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

    use App\Models\Post;
    use Illuminate\Http\RedirectResponse;
    use Illuminate\Http\Request;

    class PostController extends Controller
    {
        public function update(Request $request, Post $post): RedirectResponse
        {
            if ($request->user()->cannot('update', $post)) {
                abort(403);
            }

            // 更新貼文的處理...

            return redirect()->route('posts.index');
        }
    }
    ```
  </Tab>
</Tabs>

### 用 authorizeResource() 一次註冊 RESTful Policy

在建構子中呼叫 `authorizeResource()`，會自動將 Controller 各動作對應到 Policy 方法。

```php theme={null}
<?php

namespace App\Http\Controllers;

use App\Models\Post;

class PostController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Post::class, 'post');
    }

    // index(), show(), create(), store(), edit(), update(), destroy() 對應的
    // Policy 方法會自動套用
}
```

Controller 動作與 Policy 方法的對應關係：

| Controller 動作      | Policy 方法 |
| ------------------ | --------- |
| `index`            | `viewAny` |
| `show`             | `view`    |
| `create` / `store` | `create`  |
| `edit` / `update`  | `update`  |
| `destroy`          | `delete`  |

<Tip>
  使用 `authorizeResource()` 後，就不需要在每個動作分別呼叫 `authorize()`。與 RESTful Resource Controller 搭配特別方便。
</Tip>

## 在 Middleware 中授權

要在路由層級進行授權檢查，可使用 `can` middleware。

```php theme={null}
use App\Models\Post;

// 只有具備更新貼文權限的使用者可存取
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->middleware('can:update,post');

// 只有具備建立貼文權限的使用者可存取
Route::post('/posts', [PostController::class, 'store'])
    ->middleware('can:create,App\Models\Post');
```

使用 `can` 方法的更簡潔寫法：

```php theme={null}
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->can('update', 'post');
```

## 在 Blade 模板使用 Policy

註冊 Policy 後，Blade 的 `@can` / `@cannot` 指令也會自動使用 Policy。

```blade theme={null}
{{-- 只對有權限的使用者顯示編輯按鈕 --}}
@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}" class="btn">編輯</a>
@endcan

{{-- 刪除按鈕也一樣 --}}
@can('delete', $post)
    <form method="POST" action="{{ route('posts.destroy', $post) }}">
        @csrf
        @method('DELETE')
        <button type="submit">刪除</button>
    </form>
@endcan

{{-- 新增貼文按鈕 --}}
@can('create', App\Models\Post::class)
    <a href="{{ route('posts.create') }}">新增貼文</a>
@endcan
```

## 實務範例：部落格文章的權限管理

在部落格應用中實作「只有作者能編輯或刪除自己的文章」權限的範例。

<Steps>
  <Step title="產生 Policy">
    ```shell theme={null}
    php artisan make:policy PostPolicy --model=Post
    ```
  </Step>

  <Step title="實作 Policy 方法">
    ```php theme={null}
    <?php

    namespace App\Policies;

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

    class PostPolicy
    {
        /**
         * 賦予管理員所有權限
         */
        public function before(User $user, string $ability): bool|null
        {
            if ($user->is_admin) {
                return true;
            }

            return null;
        }

        public function viewAny(User $user): bool
        {
            return true;
        }

        public function view(User $user, Post $post): bool
        {
            return true;
        }

        public function create(User $user): bool
        {
            return true;
        }

        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;
        }
    }
    ```
  </Step>

  <Step title="在 Controller 加入 authorizeResource()">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

    use App\Models\Post;
    use Illuminate\Http\RedirectResponse;
    use Illuminate\Http\Request;
    use Illuminate\View\View;

    class PostController extends Controller
    {
        public function __construct()
        {
            $this->authorizeResource(Post::class, 'post');
        }

        public function index(): View
        {
            $posts = Post::latest()->paginate(10);
            return view('posts.index', compact('posts'));
        }

        public function store(Request $request): RedirectResponse
        {
            $post = $request->user()->posts()->create(
                $request->validate([
                    'title' => ['required', 'string', 'max:255'],
                    'body'  => ['required', 'string'],
                ])
            );

            return redirect()->route('posts.show', $post);
        }

        public function update(Request $request, Post $post): RedirectResponse
        {
            $post->update(
                $request->validate([
                    'title' => ['required', 'string', 'max:255'],
                    'body'  => ['required', 'string'],
                ])
            );

            return redirect()->route('posts.show', $post);
        }

        public function destroy(Post $post): RedirectResponse
        {
            $post->delete();

            return redirect()->route('posts.index');
        }
    }
    ```
  </Step>

  <Step title="在 Blade 模板加入權限檢查">
    ```blade theme={null}
    {{-- resources/views/posts/show.blade.php --}}
    <h1>{{ $post->title }}</h1>
    <p>{{ $post->body }}</p>
    <p>作者：{{ $post->user->name }}</p>

    @can('update', $post)
        <a href="{{ route('posts.edit', $post) }}">編輯</a>
    @endcan

    @can('delete', $post)
        <form method="POST" action="{{ route('posts.destroy', $post) }}">
            @csrf
            @method('DELETE')
            <button type="submit">刪除</button>
        </form>
    @endcan
    ```
  </Step>
</Steps>

## 總結

<AccordionGroup>
  <Accordion title="Gate 與 Policy 的選用">
    * **Gate**：不綁定特定模型的簡易權限判定。像是管理員儀表板的存取或全域設定的修改權限。
    * **Policy**：針對模型的 CRUD 操作權限管理。可依 `Post`、`Order`、`Comment` 等資源分別建立 Policy 類別。
  </Accordion>

  <Accordion title="常用 API 總覽">
    ```php theme={null}
    // 用 Gate 檢查
    Gate::allows('update-post', $post);      // true/false
    Gate::denies('update-post', $post);      // true/false
    Gate::authorize('update-post', $post);   // 失敗時拋例外

    // 用 User Model 檢查
    $user->can('update', $post);     // true/false
    $user->cannot('update', $post);  // true/false

    // 於 Controller 檢查
    Gate::authorize('update', $post);        // 失敗時 403

    // 於 Blade 檢查
    @can('update', $post) ... @endcan
    @cannot('update', $post) ... @endcannot
    ```
  </Accordion>

  <Accordion title="常用 Artisan 指令">
    ```shell theme={null}
    # 產生空的 Policy
    php artisan make:policy PostPolicy

    # 產生包含對應模型 CRUD 方法的 Policy
    php artisan make:policy PostPolicy --model=Post
    ```
  </Accordion>
</AccordionGroup>


## Related topics

- [加密（Encryption）](/zh-TW/encryption.md)
- [Laravel Telescope](/zh-TW/telescope.md)
- [Laravel Sentinel — 路由保護 Middleware 調查](/zh-TW/blog/sentinel-introduction.md)
- [Controller 的 PHP Attributes](/zh-TW/advanced/controller-attributes.md)
- [Laravel Horizon](/zh-TW/horizon.md)
