인가

번역일: 2026년 6월 25일

인가

소개

Laravel은 인증 기능 외에도, 사용자가 특정 리소스에 대해 어떤 액션을 수행할 수 있는지를 제어하는 인가(Authorization) 기능을 기본으로 제공합니다. 예를 들어, 로그인한 사용자라도 다른 사람이 작성한 게시글을 수정하거나 삭제하는 것은 허용하면 안 됩니다. Laravel의 인가 기능은 이러한 권한 검사를 체계적이고 간결하게 처리할 수 있도록 도와줍니다.

Laravel에서 인가를 처리하는 방법은 크게 두 가지입니다: GatePolicy. Gate와 Policy의 관계는 라우트와 컨트롤러의 관계와 유사합니다. Gate는 클로저(익명 함수) 기반의 단순한 인가 로직에 적합하고, Policy는 특정 모델이나 리소스와 관련된 인가 로직을 클래스 단위로 묶어서 관리할 때 사용합니다.

하나만 선택해야 하는 것은 아닙니다. 실제 애플리케이션에서는 Gate와 Policy를 함께 사용하는 경우가 많습니다. 일반적으로 Gate는 관리자 대시보드 접근처럼 특정 모델에 귀속되지 않는 액션에 어울리고, Policy는 게시글 수정·삭제처럼 특정 모델 중심의 권한 관리에 적합합니다.

Gate

Gate 작성하기

WARNING

Gate는 Laravel 인가 기능의 기본 개념을 익히기에 좋은 출발점입니다. 다만, 규모가 있는 애플리케이션을 만든다면 Policy를 활용해 인가 규칙을 체계적으로 관리하는 것을 권장합니다.

Gate는 사용자가 특정 액션을 수행할 수 있는지 판단하는 클로저입니다. 보통 App\Providers\AuthServiceProvider 클래스의 boot 메서드 안에서 Gate 파사드를 사용해 정의합니다. Gate 클로저의 첫 번째 인수는 항상 현재 인증된 사용자 인스턴스이며, 그 이후에 관련 Eloquent 모델 등 추가 인수를 받을 수 있습니다.

아래 예시는 사용자가 특정 App\Models\Post를 수정할 수 있는지 판단하는 Gate입니다. 게시글을 작성한 사용자의 id와 현재 사용자의 id를 비교합니다.

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를 정의할 수 있습니다.

use App\Policies\PostPolicy; use Illuminate\Support\Facades\Gate; public function boot(): void { Gate::define('update-post', [PostPolicy::class, 'update']); }

액션 인가하기

Gate를 사용해 액션을 인가하려면 Gate 파사드의 allows 또는 denies 메서드를 사용합니다. 현재 인증된 사용자는 자동으로 Gate 클로저에 전달되므로 별도로 넘길 필요가 없습니다. 일반적으로 컨트롤러 메서드 안에서 실제 로직을 실행하기 전에 권한을 확인합니다.

<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; 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 { if (! Gate::allows('update-post', $post)) { abort(403); } // 게시글 수정 처리... return redirect('/posts'); } }

현재 인증된 사용자가 아닌 다른 사용자에 대해 권한을 확인하고 싶다면 forUser 메서드를 사용하세요.

if (Gate::forUser($user)->allows('update-post', $post)) { // 해당 사용자는 게시글을 수정할 수 있습니다... } if (Gate::forUser($user)->denies('update-post', $post)) { // 해당 사용자는 게시글을 수정할 수 없습니다... }

any 또는 none 메서드로 여러 액션을 한 번에 확인할 수도 있습니다.

if (Gate::any(['update-post', 'delete-post'], $post)) { // 사용자가 게시글을 수정하거나 삭제할 수 있습니다... } if (Gate::none(['update-post', 'delete-post'], $post)) { // 사용자가 게시글을 수정하거나 삭제할 수 없습니다... }

인가 실패 시 예외 던지기

권한이 없을 때 자동으로 Illuminate\Auth\Access\AuthorizationException을 발생시키려면 Gate::authorize 메서드를 사용하세요. 이 예외는 Laravel의 예외 핸들러에 의해 자동으로 403 HTTP 응답으로 변환됩니다.

Gate::authorize('update-post', $post); // 인가된 경우 이 아래 로직이 실행됩니다...

추가 컨텍스트 전달하기

allows, denies, check, any, none, authorize, can, cannot 등 인가 메서드와 Blade 디렉티브(@can, @cannot, @canany)는 두 번째 인수로 배열을 받을 수 있습니다. 배열의 각 요소는 Gate 클로저의 추가 매개변수로 전달됩니다.

use App\Models\Category; use App\Models\User; use Illuminate\Support\Facades\Gate; Gate::define('create-post', function (User $user, Category $category, bool $pinned) { if (! $user->canPublishToGroup($category->group)) { return false; } elseif ($pinned && ! $user->canPinPosts()) { return false; } return true; }); if (Gate::check('create-post', [$category, $pinned])) { // 사용자가 게시글을 작성할 수 있습니다... }

Gate 응답

단순한 true/false 반환을 넘어, 오류 메시지가 포함된 상세 응답을 반환하고 싶다면 Gate에서 Illuminate\Auth\Access\Response 객체를 반환할 수 있습니다.

use App\Models\User; use Illuminate\Auth\Access\Response; use Illuminate\Support\Facades\Gate; Gate::define('edit-settings', function (User $user) { return $user->isAdmin ? Response::allow() : Response::deny('관리자만 설정을 변경할 수 있습니다.'); });

Gate::allows 메서드는 여전히 불리언 값만 반환하지만, Gate::inspect 메서드를 사용하면 Gate가 반환한 전체 응답 객체를 확인할 수 있습니다.

$response = Gate::inspect('edit-settings'); if ($response->allowed()) { // 인가되었습니다... } else { echo $response->message(); }

Gate::authorize를 사용하면, 인가 실패 시 응답 객체의 오류 메시지가 HTTP 응답에 포함되어 전달됩니다.

Gate::authorize('edit-settings'); // 인가된 경우 이 아래 로직이 실행됩니다...

HTTP 응답 상태 코드 커스터마이징

Gate에서 인가가 거부될 때 기본적으로 403 상태 코드가 반환됩니다. 다른 상태 코드를 반환하고 싶다면 Response::denyWithStatus를 사용하세요.

use App\Models\User; use Illuminate\Auth\Access\Response; use Illuminate\Support\Facades\Gate; Gate::define('edit-settings', function (User $user) { return $user->isAdmin ? Response::allow() : Response::denyWithStatus(404); });

리소스 존재 자체를 숨기기 위해 404로 응답하는 패턴은 매우 흔하므로, 이를 간편하게 처리하는 denyAsNotFound 메서드도 제공합니다.

use App\Models\User; use Illuminate\Auth\Access\Response; use Illuminate\Support\Facades\Gate; Gate::define('edit-settings', function (User $user) { return $user->isAdmin ? Response::allow() : Response::denyAsNotFound(); });

Gate 검사 가로채기

특정 사용자(예: 슈퍼 관리자)에게 모든 권한을 부여하고 싶다면, before 메서드로 다른 Gate 검사보다 먼저 실행되는 클로저를 등록할 수 있습니다.

use App\Models\User; use Illuminate\Support\Facades\Gate; Gate::before(function (User $user, string $ability) { if ($user->isAdministrator()) { return true; } });

before 클로저가 null이 아닌 값을 반환하면 해당 값이 인가 결과로 사용됩니다.

모든 Gate 검사가 끝난 에 실행되는 클로저는 after 메서드로 등록합니다.

use App\Models\User; Gate::after(function (User $user, string $ability, bool|null $result, mixed $arguments) { if ($user->isAdministrator()) { return true; } });

after 클로저도 마찬가지로 null이 아닌 값을 반환하면 그 값이 인가 결과로 사용됩니다.

인라인 인가

별도의 Gate를 정의하지 않고 즉석에서 간단하게 인가 여부를 판단하고 싶다면, Gate::allowIf 또는 Gate::denyIf 메서드를 사용하세요. 이 메서드들은 before / after 훅을 실행하지 않습니다.

use App\Models\User; use Illuminate\Support\Facades\Gate; Gate::allowIf(fn (User $user) => $user->isAdministrator()); Gate::denyIf(fn (User $user) => $user->banned());

인가되지 않거나 로그인한 사용자가 없는 경우, Laravel은 자동으로 Illuminate\Auth\Access\AuthorizationException을 발생시키며, 이는 403 HTTP 응답으로 변환됩니다.

Policy 생성하기

Policy 생성 명령어

Policy는 특정 모델이나 리소스와 관련된 인가 로직을 하나의 클래스에 묶어 관리합니다. 예를 들어, 블로그 애플리케이션이라면 App\Models\Post 모델에 대응하는 App\Policies\PostPolicy를 만들어 게시글 생성·수정 권한 등을 처리할 수 있습니다.

make:policy Artisan 명령어로 Policy 클래스를 생성합니다. 생성된 파일은 app/Policies 디렉터리에 저장되며, 해당 디렉터리가 없으면 자동으로 만들어집니다.

php artisan make:policy PostPolicy

--model 옵션을 추가하면 viewAny, view, create, update, delete 등 CRUD 관련 메서드가 미리 포함된 Policy 클래스가 생성됩니다.

php artisan make:policy PostPolicy --model=Post

Policy 등록하기

Policy 클래스를 만든 후에는 Laravel에 등록해야 합니다. App\Providers\AuthServiceProviderpolicies 배열에 Eloquent 모델 클래스와 Policy 클래스를 매핑해 등록합니다.

<?php namespace App\Providers; use App\Models\Post; use App\Policies\PostPolicy; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider { /** * 애플리케이션의 Policy 매핑 목록. * * @var array */ protected $policies = [ Post::class => PostPolicy::class, ]; /** * 인증 / 인가 서비스를 등록합니다. */ public function boot(): void { // ... } }

Policy 자동 감지

Policy를 수동으로 등록하는 대신, Laravel의 자동 감지 기능을 활용할 수도 있습니다. 자동 감지가 동작하려면 다음 네이밍 규칙을 따라야 합니다.

  • Policy 클래스는 모델 클래스가 위치한 디렉터리와 같거나 상위 디렉터리의 Policies 폴더에 위치해야 합니다.
    • 예: 모델이 app/Models에 있으면 Policy는 app/Models/Policies 또는 app/Policies에 위치할 수 있습니다.
  • Policy 클래스 이름은 모델 이름 + Policy 접미사여야 합니다.
    • 예: User 모델 → UserPolicy

자동 감지 로직을 직접 커스터마이징하려면 Gate::guessPolicyNamesUsing 메서드를 사용하세요. AuthServiceProviderboot 메서드 안에서 호출하는 것이 좋습니다.

use Illuminate\Support\Facades\Gate; Gate::guessPolicyNamesUsing(function (string $modelClass) { // 주어진 모델에 해당하는 Policy 클래스 이름을 반환합니다... });

WARNING

AuthServiceProviderpolicies 배열에 명시적으로 등록된 Policy는 자동 감지보다 항상 우선합니다.

Policy 작성하기

Policy 메서드

Policy 클래스를 등록했다면, 인가할 각 액션에 대응하는 메서드를 추가합니다. 아래는 PostPolicy에서 사용자가 특정 게시글을 수정할 수 있는지 판단하는 update 메서드 예시입니다.

<?php 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; } }

필요한 액션마다 메서드를 추가하면 됩니다. 예를 들어 view, delete 메서드를 추가해 Post 관련 다양한 인가 규칙을 처리할 수 있으며, 메서드 이름은 자유롭게 지정할 수 있습니다.

--model 옵션으로 Policy를 생성했다면 viewAny, view, create, update, delete, restore, forceDelete 메서드가 자동으로 포함됩니다.

NOTE

모든 Policy는 Laravel 서비스 컨테이너를 통해 해석되므로, Policy의 생성자에서 타입 힌트를 사용하면 필요한 의존성이 자동으로 주입됩니다.

Policy 응답

단순 불리언 외에 오류 메시지가 포함된 상세 응답이 필요하다면, Policy 메서드에서 Illuminate\Auth\Access\Response 인스턴스를 반환할 수 있습니다.

use App\Models\Post; use App\Models\User; use Illuminate\Auth\Access\Response; /** * 해당 사용자가 게시글을 수정할 수 있는지 판단합니다. */ public function update(User $user, Post $post): Response { return $user->id === $post->user_id ? Response::allow() : Response::deny('이 게시글의 작성자가 아닙니다.'); }

Policy에서 Response 객체를 반환하더라도 Gate::allows는 여전히 불리언을 반환합니다. 전체 응답 객체를 받으려면 Gate::inspect를 사용하세요.

use Illuminate\Support\Facades\Gate; $response = Gate::inspect('update', $post); if ($response->allowed()) { // 인가되었습니다... } else { echo $response->message(); }

Gate::authorize를 사용하면 인가 실패 시 응답의 오류 메시지가 HTTP 응답에 포함됩니다.

Gate::authorize('update', $post); // 인가된 경우 이 아래 로직이 실행됩니다...

HTTP 응답 상태 코드 커스터마이징

Policy에서 인가가 거부될 때 기본적으로 403이 반환됩니다. denyWithStatus로 원하는 상태 코드를 지정할 수 있습니다.

use App\Models\Post; use App\Models\User; use Illuminate\Auth\Access\Response; /** * 해당 사용자가 게시글을 수정할 수 있는지 판단합니다. */ public function update(User $user, Post $post): Response { return $user->id === $post->user_id ? Response::allow() : Response::denyWithStatus(404); }

리소스 자체를 숨기는 용도로 denyAsNotFound를 사용할 수도 있습니다.

use App\Models\Post; use App\Models\User; use Illuminate\Auth\Access\Response; /** * 해당 사용자가 게시글을 수정할 수 있는지 판단합니다. */ public function update(User $user, Post $post): Response { return $user->id === $post->user_id ? Response::allow() : Response::denyAsNotFound(); }

모델이 필요 없는 메서드

create처럼 특정 모델 인스턴스 없이 실행되는 액션에 대한 Policy 메서드는 사용자 인스턴스만 인수로 받습니다.

/** * 해당 사용자가 게시글을 작성할 수 있는지 판단합니다. */ public function create(User $user): bool { return $user->role == 'writer'; }

게스트 사용자

기본적으로 로그인하지 않은 사용자가 요청을 보내면 Gate와 Policy는 모두 자동으로 false를 반환합니다. 그러나 비로그인 사용자도 인가 검사를 통과할 수 있도록 하려면, 사용자 인수를 ?User처럼 nullable 타입으로 선언하거나 기본값으로 null을 설정하면 됩니다.

<?php 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; } }

Policy 필터

특정 사용자에게 Policy의 모든 액션을 허용하고 싶다면, Policy 클래스에 before 메서드를 정의하세요. 이 메서드는 다른 Policy 메서드보다 먼저 실행되어 사전에 인가를 결정할 수 있습니다. 주로 관리자에게 모든 권한을 부여할 때 사용합니다.

use App\Models\User; /** * 사전 인가 검사를 수행합니다. */ public function before(User $user, string $ability): bool|null { if ($user->isAdministrator()) { return true; } return null; }

특정 유형의 사용자에게 모든 인가를 거부하려면 before에서 false를 반환하세요. null을 반환하면 해당 Policy 메서드로 검사가 이어집니다.

WARNING

Policy 클래스에 확인 중인 ability 이름과 일치하는 메서드가 없으면 before 메서드는 호출되지 않습니다.

Policy를 사용한 액션 인가

User 모델을 통한 인가

Laravel의 App\Models\User 모델에는 cancannot 메서드가 내장되어 있습니다. 이 메서드에 액션 이름과 관련 모델을 전달하면, 등록된 Policy를 자동으로 찾아 실행합니다.

<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; 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('/posts'); } }

해당 모델에 Policy가 등록되어 있으면 can 메서드가 자동으로 적절한 Policy 메서드를 호출합니다. Policy가 없으면 액션 이름과 일치하는 클로저 기반 Gate를 찾아 실행합니다.

모델 인스턴스가 필요 없는 액션

create처럼 모델 인스턴스가 필요 없는 액션은 클래스 이름을 전달합니다. 어떤 Policy를 사용할지 판단하는 데 클래스 이름이 사용됩니다.

<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use App\Models\Post; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; class PostController extends Controller { /** * 게시글을 작성합니다. */ public function store(Request $request): RedirectResponse { if ($request->user()->cannot('create', Post::class)) { abort(403); } // 게시글 생성 처리... return redirect('/posts'); } }

컨트롤러 헬퍼를 통한 인가

App\Http\Controllers\Controller를 상속하는 컨트롤러에서는 authorize 메서드를 사용할 수 있습니다. 인가가 거부되면 자동으로 Illuminate\Auth\Access\AuthorizationException이 발생하고, Laravel의 예외 핸들러가 이를 403 HTTP 응답으로 변환합니다.

<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use App\Models\Post; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; class PostController extends Controller { /** * 게시글을 수정합니다. * * @throws \

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 6월 25일