메인 콘텐츠로 건너뛰기

인증과 인가의 차이

인증(Authentication) 은 사용자가 “누구인가”를 확인합니다. 로그인 처리가 그 대표입니다. 인가(Authorization) 는 그 사용자가 “무엇을 할 수 있는가”를 판단합니다. 예를 들어 자신의 게시글만 편집할 수 있고, 관리자만 설정을 변경할 수 있는 것과 같은 제어입니다. 인증으로 로그인 기능을 구현했다면 다음 단계가 인가입니다.
Laravel의 인가 기능은 게이트(Gates)와 폴리시(Policies)의 두 가지 접근 방식을 제공합니다. 어느 쪽을 사용할지는 유스케이스에 따라 판단합니다.
사용 구분의 기준
시나리오권장
특정 모델에 얽매이지 않는 단순한 판정게이트
모델에 대한 CRUD 조작의 권한 관리폴리시
관리자 대시보드에의 접근 제어게이트
블로그 기사의 투고·편집·삭제 권한폴리시

게이트(Gates)

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

Gate에 의한 인가 플로우

게이트 정의

게이트는 App\Providers\AppServiceProviderboot 메서드 내에서 Gate::define()을 사용해 정의합니다.
// 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()를 사용합니다.
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)을 쓰는 수고가 줄어듭니다.
public function update(Request $request, Post $post): RedirectResponse
{
    Gate::authorize('update-post', $post);

    // 권한이 있으면 여기에 도달합니다
    // 게시글을 업데이트하는 처리...

    return redirect('/posts');
}
Gate::authorize()는 권한이 없는 경우 Illuminate\Auth\Access\AuthorizationException을 던집니다. Laravel은 이를 자동으로 403 HTTP 응답으로 변환합니다.

관리자 우회(before 메서드)

관리자 사용자에게 모든 권한을 부여하고 싶은 경우 Gate::before()를 사용합니다.
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 디렉티브가 편리합니다.
@can('update-post', $post)
    <a href="{{ route('posts.edit', $post) }}">편집</a>
@endcan

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

폴리시(Policies)

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

Policy에 의한 인가 플로우

폴리시 생성

make:policy Artisan 명령어로 폴리시 클래스를 생성합니다.
php artisan make:policy PostPolicy
모델에 대응하는 CRUD 메서드를 모두 포함하는 골격을 생성하려면 --model 옵션을 사용합니다.
php artisan make:policy PostPolicy --model=Post
app/Policies/PostPolicy.php가 생성됩니다.

모델과 폴리시의 자동 검출

Laravel은 기본적으로 명명 규칙에 따라 폴리시를 자동으로 검출합니다.
  • 모델: app/Models/Post.php
  • 폴리시: app/Policies/PostPolicy.php
이 명명 규칙을 지키면 폴리시의 등록 작업은 불필요합니다.
명명 규칙에 따르지 않거나 수동으로 등록하고 싶은 경우, AppServiceProviderboot 메서드에서 Gate::policy()를 사용합니다.
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;

Gate::policy(Post::class, PostPolicy::class);
Laravel 13에서는 모델에 #[UsePolicy] 어트리뷰트를 붙여 선언적으로 등록할 수도 있습니다.
use App\Policies\PostPolicy;
use Illuminate\Database\Eloquent\Attributes\UsePolicy;

#[UsePolicy(PostPolicy::class)]
class Post extends Model {}

폴리시 메서드 구현

--model 옵션으로 생성한 폴리시에는 표준적인 CRUD 액션에 대응하는 메서드가 포함되어 있습니다.
<?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 메서드를 정의함으로써 관리자에게 모든 권한을 부여할 수 있습니다.
/**
 * 다른 폴리시 메서드보다 먼저 실행되는 사전 체크
 * $ability 에는 폴리시 메서드명('update', 'delete' 등)이 들어감
 */
public function before(User $user, string $ability): bool|null
{
    if ($user->isAdministrator()) {
        return true; // 관리자는 모든 조작을 허가
    }

    return null; // null 을 반환하면 일반 폴리시 메서드로 진행
}
before 메서드는 폴리시 클래스에 대응하는 메서드가 존재하는 경우에만 호출됩니다. 예를 들어 update 메서드가 존재하지 않는 경우 before도 호출되지 않습니다.

before 콜백의 실행 순서

컨트롤러에서 폴리시 사용

authorize() 메서드

Laravel의 컨트롤러에는 authorize() 헬퍼 메서드가 있습니다(베이스 컨트롤러를 사용하지 않는 경우는 Gate::authorize()를 사용합니다).
<?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');
    }
}

authorizeResource()로 RESTful 폴리시 일괄 등록

authorizeResource()를 생성자에서 호출하면 컨트롤러의 각 액션에 대응하는 폴리시 메서드를 자동으로 연결합니다.
<?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() 에
    // 대응하는 폴리시 메서드가 자동으로 적용됨
}
컨트롤러 액션과 폴리시 메서드의 대응 관계:
컨트롤러 액션폴리시 메서드
indexviewAny
showview
create / storecreate
edit / updateupdate
destroydelete
authorizeResource()를 사용하면 각 액션에 개별로 authorize()를 쓸 필요가 없어집니다. RESTful한 리소스 컨트롤러와 조합하면 특히 편리합니다.

미들웨어에서의 인가

라우트 레벨에서 인가 체크를 하려면 can 미들웨어를 사용합니다.
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 메서드를 사용한 보다 간결한 기술:
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->can('update', 'post');

Blade 템플릿에서 폴리시 사용

폴리시를 등록하면 Blade의 @can / @cannot 디렉티브에서도 폴리시가 자동으로 사용됩니다.
{{-- 게시글의 편집 버튼은 권한을 가진 사용자에게만 표시 --}}
@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

실전 예시: 블로그 기사의 권한 관리

블로그 앱에서 “투고자만 자신의 기사를 편집·삭제할 수 있는” 권한을 구현하는 예시입니다.
1

폴리시를 생성

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

폴리시 메서드를 구현

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

컨트롤러에 authorizeResource() 를 추가

<?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');
    }
}
4

Blade 템플릿에 권한 체크를 추가

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

정리

  • 게이트: 특정 모델에 얽매이지 않는 단순한 권한 판정. 관리자 대시보드에의 접근이나 글로벌한 설정 변경 권한 등.
  • 폴리시: 모델에 대한 CRUD 조작의 권한 관리. Post, Order, Comment 같은 리소스별로 폴리시 클래스를 작성.
// 게이트로 확인
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
# 빈 폴리시 생성
php artisan make:policy PostPolicy

# 모델에 대응하는 CRUD 메서드 포함하여 생성
php artisan make:policy PostPolicy --model=Post
마지막 수정일 2026년 7월 13일