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

# 인가(게이트와 폴리시)

> Laravel의 게이트와 폴리시를 사용해 사용자의 액션 권한을 관리하는 방법을 설명합니다.

## 인증과 인가의 차이

**인증(Authentication)** 은 사용자가 "누구인가"를 확인합니다. 로그인 처리가 그 대표입니다.
**인가(Authorization)** 는 그 사용자가 "무엇을 할 수 있는가"를 판단합니다. 예를 들어 자신의 게시글만 편집할 수 있고, 관리자만 설정을 변경할 수 있는 것과 같은 제어입니다.

[인증](./authentication)으로 로그인 기능을 구현했다면 다음 단계가 인가입니다.

<Info>
  Laravel의 인가 기능은 게이트(Gates)와 폴리시(Policies)의 두 가지 접근 방식을 제공합니다. 어느 쪽을 사용할지는 유스케이스에 따라 판단합니다.
</Info>

**사용 구분의 기준**

| 시나리오                  | 권장  |
| --------------------- | --- |
| 특정 모델에 얽매이지 않는 단순한 판정 | 게이트 |
| 모델에 대한 CRUD 조작의 권한 관리 | 폴리시 |
| 관리자 대시보드에의 접근 제어      | 게이트 |
| 블로그 기사의 투고·편집·삭제 권한   | 폴리시 |

## 게이트(Gates)

게이트는 클로저 기반의 단순한 인가 체크입니다. 특정 모델에 얽매이지 않는 권한 판정에 적합합니다.

### Gate에 의한 인가 플로우

```mermaid theme={null}
flowchart TD
    A["AppServiceProvider::boot()<br>(App\\Providers\\AppServiceProvider)"] --> B["Gate::define('ability', fn)<br>인가 로직을 등록"]
    C["컨트롤러에서 인가 체크"] --> D{"Gate::before()<br>가 설정되어 있는가?"}
    D -->|"예"| E{"before() 의 반환값"}
    E -->|"true / false"| F["before() 의 결과를 최종 판정에 사용"]
    E -->|"null"| G["define() 의 클로저를 실행"]
    D -->|"아니오"| G
    G --> H{"클로저의 반환값"}
    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"]
```

### 게이트 정의

게이트는 `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::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 에는 실행하려는 게이트명('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`의 클로저가 `null` 이외의 값을 반환하면 그 결과가 최종적인 권한 판정이 됩니다. `null`을 반환하거나 아무것도 반환하지 않는 경우 일반적인 게이트 판정으로 진행합니다.

### Blade 템플릿에서의 @can / @cannot

템플릿에서 게이트 판정을 사용하려면 `@can`과 `@cannot` 디렉티브가 편리합니다.

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

@cannot('update-post', $post)
    <p>이 게시글을 편집할 권한이 없습니다.</p>
@endcannot
```

## 폴리시(Policies)

폴리시는 특정 모델에 관련된 인가 로직을 클래스로 정리한 것입니다. `Post` 모델에 대한 작성·열람·편집·삭제의 권한 관리에는 게이트보다 폴리시가 적합합니다.

### Policy에 의한 인가 플로우

```mermaid theme={null}
flowchart TD
    A["HTTP 요청"] --> B["컨트롤러"]
    B --> C["Gate::authorize('update', $post)"]
    C --> D{"PostPolicy::before()<br>가 정의되어 있는가?"}
    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
```

### 폴리시 생성

`make:policy` Artisan 명령어로 폴리시 클래스를 생성합니다.

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

모델에 대응하는 CRUD 메서드를 모두 포함하는 골격을 생성하려면 `--model` 옵션을 사용합니다.

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

`app/Policies/PostPolicy.php`가 생성됩니다.

### 모델과 폴리시의 자동 검출

Laravel은 기본적으로 명명 규칙에 따라 폴리시를 자동으로 검출합니다.

* 모델: `app/Models/Post.php`
* 폴리시: `app/Policies/PostPolicy.php`

이 명명 규칙을 지키면 폴리시의 등록 작업은 불필요합니다.

<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>

### 폴리시 메서드 구현

`--model` 옵션으로 생성한 폴리시에는 표준적인 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 메서드)

폴리시에서도 `before` 메서드를 정의함으로써 관리자에게 모든 권한을 부여할 수 있습니다.

```php theme={null}
/**
 * 다른 폴리시 메서드보다 먼저 실행되는 사전 체크
 * $ability 에는 폴리시 메서드명('update', 'delete' 등)이 들어감
 */
public function before(User $user, string $ability): bool|null
{
    if ($user->isAdministrator()) {
        return true; // 관리자는 모든 조작을 허가
    }

    return null; // null 을 반환하면 일반 폴리시 메서드로 진행
}
```

<Warning>
  `before` 메서드는 폴리시 클래스에 대응하는 메서드가 존재하는 경우에만 호출됩니다. 예를 들어 `update` 메서드가 존재하지 않는 경우 `before`도 호출되지 않습니다.
</Warning>

### before 콜백의 실행 순서

```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["일반 폴리시 메서드를 실행<br>update() / delete() 등"]
    E --> F{"폴리시 메서드의 반환값"}
    F -->|"true"| G["✓ 접근 허가"]
    F -->|"false"| H["✗ 접근 거부 → 403"]
    D -->|"true (관리자 우회)"| G
    D -->|"false"| H
```

## 컨트롤러에서 폴리시 사용

### authorize() 메서드

Laravel의 컨트롤러에는 `authorize()` 헬퍼 메서드가 있습니다(베이스 컨트롤러를 사용하지 않는 경우는 `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 폴리시 일괄 등록

`authorizeResource()`를 생성자에서 호출하면 컨트롤러의 각 액션에 대응하는 폴리시 메서드를 자동으로 연결합니다.

```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() 에
    // 대응하는 폴리시 메서드가 자동으로 적용됨
}
```

컨트롤러 액션과 폴리시 메서드의 대응 관계:

| 컨트롤러 액션            | 폴리시 메서드   |
| ------------------ | --------- |
| `index`            | `viewAny` |
| `show`             | `view`    |
| `create` / `store` | `create`  |
| `edit` / `update`  | `update`  |
| `destroy`          | `delete`  |

<Tip>
  `authorizeResource()`를 사용하면 각 액션에 개별로 `authorize()`를 쓸 필요가 없어집니다. RESTful한 리소스 컨트롤러와 조합하면 특히 편리합니다.
</Tip>

## 미들웨어에서의 인가

라우트 레벨에서 인가 체크를 하려면 `can` 미들웨어를 사용합니다.

```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 템플릿에서 폴리시 사용

폴리시를 등록하면 Blade의 `@can` / `@cannot` 디렉티브에서도 폴리시가 자동으로 사용됩니다.

```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="폴리시를 생성">
    ```shell theme={null}
    php artisan make:policy PostPolicy --model=Post
    ```
  </Step>

  <Step title="폴리시 메서드를 구현">
    ```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="컨트롤러에 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="게이트와 폴리시의 사용 구분">
    * **게이트**: 특정 모델에 얽매이지 않는 단순한 권한 판정. 관리자 대시보드에의 접근이나 글로벌한 설정 변경 권한 등.
    * **폴리시**: 모델에 대한 CRUD 조작의 권한 관리. `Post`, `Order`, `Comment` 같은 리소스별로 폴리시 클래스를 작성.
  </Accordion>

  <Accordion title="자주 사용하는 API 정리">
    ```php theme={null}
    // 게이트로 확인
    Gate::allows('update-post', $post);      // true/false
    Gate::denies('update-post', $post);      // true/false
    Gate::authorize('update-post', $post);   // 실패 시 예외

    // 사용자 모델로 확인
    $user->can('update', $post);     // true/false
    $user->cannot('update', $post);  // true/false

    // 컨트롤러에서 확인
    Gate::authorize('update', $post);        // 실패 시 403

    // Blade 로 확인
    @can('update', $post) ... @endcan
    @cannot('update', $post) ... @endcannot
    ```
  </Accordion>

  <Accordion title="자주 사용하는 Artisan 명령어">
    ```shell theme={null}
    # 빈 폴리시 생성
    php artisan make:policy PostPolicy

    # 모델에 대응하는 CRUD 메서드 포함하여 생성
    php artisan make:policy PostPolicy --model=Post
    ```
  </Accordion>
</AccordionGroup>


## Related topics

- [암호화(Encryption)](/ko/encryption.md)
- [에러 핸들링](/ko/error-handling.md)
- [Laravel Horizon](/ko/horizon.md)
- [튜토리얼 - Laravel Console Starter](/ko/packages/laravel-console-starter/tutorial.md)
- [PHP 어트리뷰트](/ko/advanced/php-attributes.md)
