본문 바로가기

인가

번역일: 2026년 6월 21일

인가

소개

Laravel은 인증 기능 외에도, 특정 리소스에 대한 사용자 액션을 제어하는 인가(Authorization) 기능을 제공합니다. 예를 들어, 로그인한 사용자라도 다른 사용자의 게시글을 수정하거나 삭제할 수 없어야 합니다. Laravel의 인가 기능은 이런 권한 검사를 체계적으로 관리할 수 있게 해줍니다.

Laravel의 인가 방법은 크게 두 가지입니다: GatePolicy. Gate와 Policy의 관계는 라우트와 컨트롤러의 관계와 비슷하게 생각할 수 있습니다. Gate는 클로저 기반의 단순한 인가 방식이고, Policy는 컨트롤러처럼 특정 모델이나 리소스를 중심으로 인가 로직을 묶어서 관리하는 방식입니다.

Gate와 Policy 중 하나만 골라 써야 하는 것은 아닙니다. 실제 애플리케이션에서는 두 방식을 함께 사용하는 경우가 많습니다. Gate는 관리자 대시보드 접근처럼 특정 모델과 직접적인 관련이 없는 액션에 적합하고, Policy는 게시글 수정·삭제처럼 특정 모델에 대한 권한 판단에 적합합니다.

Gate

Gate 작성

WARNING

Gate는 Laravel 인가 기능의 기본 개념을 익히기에 좋은 방법입니다. 하지만 규모 있는 애플리케이션을 만들 때는 인가 규칙을 더 체계적으로 관리할 수 있는 Policy를 사용하는 것을 권장합니다.

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

아래 예시는 사용자가 특정 Post를 수정할 수 있는지 판단하는 Gate입니다. 게시글 작성자의 user_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 응답

지금까지는 Gate가 단순히 true/false를 반환하는 경우만 살펴봤습니다. 하지만 인가 거부 시 구체적인 오류 메시지를 함께 반환하고 싶다면 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의 전체 응답 객체가 필요하다면 Gate::inspect를 사용하세요:

$response = Gate::inspect('edit-settings');

if ($response->allowed()) {
    // 인가됨...
} else {
    echo $response->message();
}

Gate::authorize를 사용할 경우, 인가 응답에 포함된 오류 메시지가 HTTP 응답에 그대로 전달됩니다:

Gate::authorize('edit-settings');

// 인가된 경우 이 아래 코드가 실행됨...

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

Gate에서 액션이 거부되면 기본적으로 403 응답이 반환됩니다. 상황에 따라 다른 HTTP 상태 코드를 반환하고 싶다면 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 클로저의 반환값은 Gate나 Policy가 null을 반환한 경우에만 최종 인가 결과를 덮어씁니다.

인라인 인가

별도의 Gate를 정의하지 않고 즉석에서 인가 검사를 수행하고 싶다면 Gate::allowIfGate::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 옵션을 추가하면 조회, 생성, 수정, 삭제 관련 예시 메서드가 포함된 Policy 클래스가 생성됩니다:

php artisan make:policy PostPolicy --model=Post

Policy 등록

자동 Policy 탐색

Laravel은 모델과 Policy가 네이밍 컨벤션을 따를 경우 자동으로 Policy를 탐색합니다. 구체적으로 Policy 클래스는 모델이 있는 디렉터리와 같거나 상위 디렉터리의 Policies 폴더에 위치해야 합니다.

예를 들어 모델이 app/Models에 있다면, Laravel은 app/Models/Policiesapp/Policies 순서로 Policy를 찾습니다. Policy 이름은 모델 이름에 Policy를 붙인 형태여야 합니다(User 모델 → UserPolicy).

자동 탐색 로직을 직접 정의하려면 Gate::guessPolicyNamesUsing 메서드로 커스텀 콜백을 등록할 수 있습니다. 이는 보통 AppServiceProviderboot 메서드에서 호출합니다:

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

수동 Policy 등록

AppServiceProviderboot 메서드에서 Gate 파사드를 사용해 Policy를 직접 등록할 수도 있습니다:

use App\Models\Order; use App\Policies\OrderPolicy; use Illuminate\Support\Facades\Gate; /** * 애플리케이션 서비스 초기화 */ public function boot(): void { Gate::policy(Order::class, OrderPolicy::class); }

Policy 작성

Policy 메서드

Policy 클래스가 등록되면 인가할 각 액션에 대한 메서드를 추가합니다. 예를 들어 PostPolicyupdate 메서드를 정의해 특정 사용자가 특정 게시글을 수정할 수 있는지 판단할 수 있습니다.

update 메서드는 UserPost 인스턴스를 인수로 받아 사용자의 id와 게시글의 user_id가 일치하는지 확인합니다:

<?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 등의 메서드를 자유롭게 추가할 수 있습니다. --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('본인이 작성한 게시글만 수정할 수 있습니다.'); }

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); }

리소스의 존재를 숨기는 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처럼 옵셔널 타입으로 선언하거나 기본값 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 메서드를 정의합니다. before 메서드는 다른 Policy 메서드보다 먼저 실행되므로, 의도된 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

before 메서드는 검사 중인 ability 이름과 일치하는 메서드가 Policy 클래스에 존재할 때만 호출됩니다.

Policy를 사용한 액션 인가

User 모델을 통한 인가

Laravel의 App\Models\User 모델에는 인가를 위한 cancannot 메서드가 포함되어 있습니다. 이 메서드는 인가할 액션 이름과 관련 모델을 인수로 받습니다. 보통 컨트롤러 메서드 안에서 사용합니다:

<?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처럼 모델 인스턴스가 필요 없는 경우에는 클래스 이름을 can 메서드에 전달합니다. 이때 클래스 이름을 기준으로 어떤 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'); } }

Gate 파사드를 통한 인가

User 모델의 can 메서드 외에도 Gate 파사드의 authorize 메서드를 사용해 인가할 수 있습니다.

authorize는 인가 실패 시 자동으로 Illuminate\Auth\Access\AuthorizationException을 던지고, 이는 Laravel 예외 핸들러에 의해 403 응답으로 변환됩니다:

<?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 { /** * 게시글 수정 * * @throws \Illuminate\Auth\Access\AuthorizationException */ public function update(Request $request, Post $post): RedirectResponse { Gate::authorize('update', $post); // 인가된 사용자만 이 아래 코드를 실행할 수 있음... return redirect('/posts'); } }

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

마찬가지로 create처럼 모델 인스턴스가 필요 없는 경우 클래스 이름을 authorize에 전달합니다:

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

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

번역일: 2026년 6월 21일