Laravel Sanctum

번역일: 2026년 6월 27일

Laravel Sanctum

소개

Laravel Sanctum은 SPA(싱글 페이지 애플리케이션), 모바일 애플리케이션, 그리고 토큰 기반 API를 위한 간결한 인증 시스템입니다. Sanctum을 사용하면 각 사용자가 자신의 계정에 대해 여러 개의 API 토큰을 발급할 수 있으며, 토큰마다 허용할 동작을 권한(ability/scope)으로 세밀하게 제어할 수 있습니다.

동작 원리

Sanctum은 서로 다른 두 가지 문제를 해결하기 위해 만들어졌습니다. 라이브러리를 깊이 파고들기 전에 각각을 먼저 살펴보겠습니다.

API 토큰

첫째, Sanctum은 OAuth의 복잡함 없이 사용자에게 API 토큰을 발급할 수 있는 단순한 패키지입니다. GitHub 등의 서비스에서 제공하는 "개인 액세스 토큰"에서 영감을 받았습니다. 예를 들어, 애플리케이션의 "계정 설정" 화면에서 사용자가 직접 API 토큰을 생성할 수 있는 UI를 만들 때 Sanctum으로 토큰 발급과 관리를 처리할 수 있습니다. 이러한 토큰은 보통 만료 기간이 매우 길지만(수년), 사용자가 언제든지 직접 폐기할 수 있습니다.

Sanctum은 사용자 API 토큰을 단일 데이터베이스 테이블에 저장하고, 유효한 API 토큰이 포함된 Authorization 헤더를 통해 들어오는 HTTP 요청을 인증합니다.

SPA 인증

둘째, Sanctum은 Laravel API와 통신해야 하는 SPA를 간단하게 인증하는 방법을 제공합니다. 해당 SPA는 Laravel 애플리케이션과 동일한 저장소에 있을 수도 있고, Next.js나 Nuxt로 만든 것처럼 완전히 별개의 저장소에 있을 수도 있습니다.

이 기능에서 Sanctum은 토큰을 전혀 사용하지 않습니다. 대신 Laravel에 내장된 쿠키 기반 세션 인증 서비스를 활용합니다. 구체적으로는 Laravel의 web 인증 가드를 사용하며, 덕분에 CSRF 보호, 세션 인증, XSS를 통한 인증 정보 유출 방지 등의 이점을 그대로 누릴 수 있습니다.

Sanctum은 요청이 자체 SPA 프론트엔드에서 온 경우에만 쿠키 인증을 시도합니다. 들어오는 HTTP 요청을 검사할 때, 먼저 인증 쿠키를 확인하고, 쿠키가 없으면 Authorization 헤더에서 유효한 API 토큰을 찾습니다.

NOTE

Sanctum을 API 토큰 인증 전용으로만, 또는 SPA 인증 전용으로만 사용해도 전혀 문제없습니다. 두 기능을 모두 사용할 의무는 없습니다.

설치

install:api Artisan 명령어로 Laravel Sanctum을 설치할 수 있습니다:

php artisan install:api

SPA 인증에 Sanctum을 활용할 계획이라면, 이 문서의 SPA 인증 섹션을 참고하세요.

설정

기본 모델 재정의

꼭 필요한 경우는 아니지만, Sanctum이 내부적으로 사용하는 PersonalAccessToken 모델을 자유롭게 확장할 수 있습니다:

use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken; class PersonalAccessToken extends SanctumPersonalAccessToken { // ... }

커스텀 모델을 만들었다면, AppServiceProviderboot 메서드에서 usePersonalAccessTokenModel 메서드를 호출하여 Sanctum이 해당 모델을 사용하도록 지정합니다:

use App\Models\Sanctum\PersonalAccessToken; use Laravel\Sanctum\Sanctum; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); }

API 토큰 인증

NOTE

자사(first-party) SPA를 인증할 때는 API 토큰을 사용하지 마세요. 대신 Sanctum의 내장 SPA 인증 기능을 사용하세요.

API 토큰 발급

Sanctum으로 API 토큰(개인 액세스 토큰)을 발급하면 이를 사용해 애플리케이션의 API 요청을 인증할 수 있습니다. 토큰을 포함한 요청은 Authorization 헤더에 Bearer 토큰 형태로 전달해야 합니다.

토큰을 발급하려면, 먼저 User 모델에 Laravel\Sanctum\HasApiTokens 트레이트를 추가합니다:

use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; }

토큰은 createToken 메서드로 발급합니다. 이 메서드는 Laravel\Sanctum\NewAccessToken 인스턴스를 반환합니다. API 토큰은 데이터베이스에 저장되기 전에 SHA-256으로 해시 처리되지만, NewAccessToken 인스턴스의 plainTextToken 프로퍼티를 통해 평문 토큰 값에 접근할 수 있습니다. 토큰이 생성된 직후 이 값을 사용자에게 바로 보여줘야 합니다:

use Illuminate\Http\Request; Route::post('/tokens/create', function (Request $request) { $token = $request->user()->createToken($request->token_name); return ['token' => $token->plainTextToken]; });

HasApiTokens 트레이트가 제공하는 tokens Eloquent 관계를 통해 사용자의 모든 토큰에 접근할 수 있습니다:

foreach ($user->tokens as $token) { // ... }

토큰 권한(Abilities)

Sanctum은 토큰에 "권한(abilities)"을 부여할 수 있습니다. OAuth의 "스코프"와 유사한 개념입니다. createToken 메서드의 두 번째 인수로 권한 문자열 배열을 전달하면 됩니다:

return $user->createToken('token-name', ['server:update'])->plainTextToken;

Sanctum으로 인증된 요청을 처리할 때는 tokenCan 또는 tokenCant 메서드로 해당 토큰에 특정 권한이 있는지 확인할 수 있습니다:

if ($user->tokenCan('server:update')) { // ... } if ($user->tokenCant('server:update')) { // ... }

토큰 권한 미들웨어

Sanctum은 들어오는 요청의 토큰에 특정 권한이 부여되어 있는지 검사하는 두 가지 미들웨어를 제공합니다. 먼저 bootstrap/app.php 파일에 미들웨어 별칭을 정의합니다:

use Laravel\Sanctum\Http\Middleware\CheckAbilities; use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility; ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ 'abilities' => CheckAbilities::class, 'ability' => CheckForAnyAbility::class, ]); })

abilities 미들웨어는 요청 토큰이 나열된 권한을 모두 가지고 있는지 검사합니다:

Route::get('/orders', function () { // 토큰이 "check-status"와 "place-orders" 권한을 모두 가지고 있어야 합니다... })->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);

ability 미들웨어는 요청 토큰이 나열된 권한 중 하나 이상을 가지고 있는지 검사합니다:

Route::get('/orders', function () { // 토큰이 "check-status" 또는 "place-orders" 권한 중 하나를 가지고 있으면 됩니다... })->middleware(['auth:sanctum', 'ability:check-status,place-orders']);

자사 UI에서 시작된 요청

편의상, Sanctum의 내장 SPA 인증을 사용하고 있고 인증된 요청이 자사 SPA에서 온 경우라면, tokenCan 메서드는 항상 true를 반환합니다.

하지만 이것이 사용자가 해당 작업을 반드시 수행할 수 있다는 의미는 아닙니다. 실제 허용 여부는 애플리케이션의 인가 정책에서 토큰 권한과 사용자 자체의 권한을 함께 확인하여 결정해야 합니다.

예를 들어, 서버를 관리하는 애플리케이션이라면 토큰에 서버 업데이트 권한이 있는지, 그리고 해당 서버가 현재 사용자의 것인지를 모두 확인해야 합니다:

return $request->user()->id === $server->user_id && $request->user()->tokenCan('server:update')

자사 UI 요청에서 tokenCan이 항상 true를 반환하도록 허용하는 것이 처음에는 이상하게 느껴질 수 있습니다. 하지만 이 방식 덕분에 API 토큰이 항상 존재한다고 가정하고 인가 정책 안에서 어디서든 tokenCan을 안전하게 호출할 수 있습니다. 요청이 UI에서 온 것인지 서드파티 API 소비자에서 온 것인지 걱정하지 않아도 됩니다.

라우트 보호

모든 들어오는 요청에 인증을 요구하려면, routes/web.phproutes/api.php 라우트 파일에서 보호할 라우트에 sanctum 인증 가드를 연결합니다. 이 가드는 요청이 상태 유지(stateful) 쿠키 인증 요청인지, 또는 서드파티에서 온 유효한 API 토큰 헤더가 포함된 요청인지 검사합니다.

routes/web.php에서도 sanctum 가드를 사용하는 이유가 궁금할 수 있습니다. Sanctum은 먼저 Laravel의 일반 세션 인증 쿠키로 요청을 인증하려 시도하고, 쿠키가 없으면 Authorization 헤더의 토큰으로 인증을 시도합니다. 모든 요청에 Sanctum을 사용하면 현재 인증된 사용자 인스턴스에서 언제든지 tokenCan을 호출할 수 있다는 장점도 있습니다:

use Illuminate\Http\Request; Route::get('/user', function (Request $request) { return $request->user(); })->middleware('auth:sanctum');

토큰 폐기

HasApiTokens 트레이트가 제공하는 tokens 관계를 이용해 데이터베이스에서 토큰을 삭제함으로써 토큰을 "폐기"할 수 있습니다:

// 모든 토큰 폐기... $user->tokens()->delete(); // 현재 요청 인증에 사용된 토큰만 폐기... $request->user()->currentAccessToken()->delete(); // 특정 토큰 폐기... $user->tokens()->where('id', $tokenId)->delete();

토큰 만료

기본적으로 Sanctum 토큰은 만료되지 않으며, 토큰 폐기를 통해서만 무효화할 수 있습니다. 만료 시간을 설정하고 싶다면, sanctum 설정 파일의 expiration 옵션을 사용합니다. 이 값은 토큰 발급 후 만료까지의 시간을 분(minute) 단위로 지정합니다:

'expiration' => 525600,

토큰마다 만료 시간을 개별적으로 지정하려면, createToken 메서드의 세 번째 인수로 만료 시각을 전달합니다:

return $user->createToken( 'token-name', ['*'], now()->addWeek() )->plainTextToken;

토큰 만료 시간을 설정했다면, 만료된 토큰을 주기적으로 정리하는 스케줄 작업을 등록하는 것이 좋습니다. Sanctum에는 이를 위한 sanctum:prune-expired Artisan 명령어가 내장되어 있습니다. 예를 들어, 24시간 이상 만료된 토큰 레코드를 매일 삭제하도록 설정할 수 있습니다:

use Illuminate\Support\Facades\Schedule; Schedule::command('sanctum:prune-expired --hours=24')->daily();

SPA 인증

Sanctum은 Laravel API와 통신해야 하는 SPA를 인증하는 간단한 방법도 제공합니다. SPA는 Laravel 애플리케이션과 같은 저장소에 있을 수도 있고, Next.js나 Nuxt처럼 완전히 별개의 저장소에 있을 수도 있습니다.

이 기능에서 Sanctum은 토큰을 사용하지 않고, Laravel의 쿠키 기반 세션 인증 서비스를 사용합니다. 이 방식은 CSRF 보호, 세션 인증, XSS를 통한 인증 정보 유출 방지 등의 이점을 제공합니다.

WARNING

인증이 동작하려면 SPA와 API가 동일한 최상위 도메인을 공유해야 합니다. 단, 서로 다른 서브도메인에 위치하는 것은 괜찮습니다. 또한 요청 시 반드시 Accept: application/json 헤더와 함께 Referer 또는 Origin 헤더를 포함해야 합니다.

설정

자사 도메인 설정

먼저 SPA가 요청을 보내는 도메인을 설정해야 합니다. sanctum 설정 파일의 stateful 옵션에 해당 도메인을 지정합니다. 이 설정은 API에 요청할 때 Laravel 세션 쿠키로 "상태 유지(stateful)" 인증을 유지할 도메인을 결정합니다.

WARNING

포트가 포함된 URL(예: 127.0.0.1:8000)로 애플리케이션에 접근하는 경우, 도메인에 포트 번호도 함께 포함해야 합니다.

Sanctum 미들웨어

다음으로, SPA에서 온 요청은 Laravel 세션 쿠키로 인증하면서, 서드파티나 모바일 앱에서 온 요청은 API 토큰으로 인증할 수 있도록 설정해야 합니다. bootstrap/app.php 파일에서 statefulApi 미들웨어 메서드를 호출하면 간단하게 처리됩니다:

->withMiddleware(function (Middleware $middleware) { $middleware->statefulApi(); })

CORS와 쿠키

별도의 서브도메인에서 실행되는 SPA로 인증할 때 문제가 생긴다면, CORS(교차 출처 리소스 공유) 또는 세션 쿠키 설정이 잘못되었을 가능성이 높습니다.

config/cors.php 설정 파일은 기본적으로 공개되어 있지 않습니다. CORS 옵션을 직접 수정해야 한다면, config:publish Artisan 명령어로 설정 파일을 먼저 생성하세요:

php artisan config:publish cors

이후 config/cors.phpsupports_credentials 옵션을 true로 설정하여 Access-Control-Allow-Credentials 헤더가 True 값으로 응답에 포함되도록 합니다.

또한 프론트엔드의 전역 axios 인스턴스에서 withCredentialswithXSRFToken 옵션을 활성화해야 합니다. 보통 resources/js/bootstrap.js 파일에서 설정합니다. Axios를 사용하지 않는다면 사용 중인 HTTP 클라이언트에서 동일하게 설정하세요:

axios.defaults.withCredentials = true; axios.defaults.withXSRFToken = true;

마지막으로, config/session.php의 세션 쿠키 도메인 설정이 루트 도메인의 모든 서브도메인을 지원하도록, 도메인 앞에 .을 붙여주세요:

'domain' => '.domain.com',

인증하기

CSRF 보호

SPA를 인증하려면, SPA의 "로그인" 페이지에서 먼저 /sanctum/csrf-cookie 엔드포인트에 요청을 보내 CSRF 보호를 초기화해야 합니다:

axios.get('/sanctum/csrf-cookie').then(response => { // 로그인 처리... });

이 요청에서 Laravel은 현재 CSRF 토큰이 담긴 XSRF-TOKEN 쿠키를 설정합니다. 이후 요청에서는 이 토큰을 URL 디코딩하여 X-XSRF-TOKEN 헤더에 포함해야 합니다. Axios나 Angular의 HttpClient 같은 일부 HTTP 클라이언트는 이를 자동으로 처리해 줍니다. 자동 처리가 되지 않는 라이브러리를 사용한다면, XSRF-TOKEN 쿠키의 URL 디코딩 값을 X-XSRF-TOKEN 헤더에 직접 설정해야 합니다.

로그인

CSRF 보호 초기화가 완료되면, Laravel 애플리케이션의 /login 라우트에 POST 요청을 보냅니다. 이 /login 라우트는 직접 구현하거나 Laravel Fortify 같은 헤드리스 인증 패키지를 사용해 구현할 수 있습니다.

로그인에 성공하면 인증 상태가 유지되고, 이후 모든 요청은 Laravel이 발급한 세션 쿠키를 통해 자동으로 인증됩니다. 또한, 이미 /sanctum/csrf-cookie에 요청했기 때문에 JavaScript HTTP 클라이언트가 XSRF-TOKEN 쿠키 값을 X-XSRF-TOKEN 헤더에 포함하는 한 이후 요청도 자동으로 CSRF 보호를 받습니다.

활동이 없어 사용자 세션이 만료되면 이후 요청에서 401 또는 419 HTTP 오류 응답을 받을 수 있습니다. 이 경우 사용자를 SPA의 로그인 페이지로 리다이렉트해야 합니다.

WARNING

/login 엔드포인트를 직접 작성해도 됩니다. 단, Laravel이 제공하는 표준 세션 기반 인증 서비스를 사용해 사용자를 인증해야 합니다. 일반적으로 web 인증 가드를 사용하면 됩니다.

라우트 보호

모든 들어오는 요청에 인증을 요구하려면, routes/api.php 파일의 API 라우트에 sanctum 인증 가드를 연결합니다. 이 가드는 SPA에서 온 상태 유지 인증 요청인지, 또는 서드파티에서 온 유효한 API 토큰 헤더가 포함된 요청인지를 검사합니다:

use Illuminate\Http\Request; Route::get('/user', function (Request $request) { return $request->user(); })->middleware('auth:sanctum');

프라이빗 브로드캐스트 채널 인가

SPA에서 프라이빗/프레즌스 브로드캐스트 채널을 인증해야 한다면, bootstrap/app.phpwithRouting 메서드에서 channels 항목을 제거하고, withBroadcasting 메서드를 별도로 호출하여 브로드캐스팅 라우트에 적절한 미들웨어를 지정해야 합니다:

return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', // ... ) ->withBroadcasting( __DIR__.'/../routes/channels.php', ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']], )

다음으로, Pusher의 인가 요청이 성공하려면 Laravel Echo 초기화 시 커스텀 Pusher authorizer를 제공해야 합니다. 이를 통해 크로스 도메인 요청에 맞게 설정된 axios 인스턴스를 사용하도록 Pusher를 구성할 수 있습니다:

window.Echo = new Echo({ broadcaster: "pusher", cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, encrypted: true, key: import.meta.env.VITE_PUSHER_APP_KEY, authorizer: (channel, options) => { return { authorize: (socketId, callback) => { axios.post('/api/broadcasting/auth', { socket_id: socketId, channel_name: channel.name }) .then(response => { callback(false, response.data); }) .catch(error => { callback(true, error); }); } }; }, })

모바일 애플리케이션 인증

Sanctum 토큰은 모바일 애플리케이션의 API 요청 인증에도 사용할 수 있습니다. 모바일 앱 요청 인증 방식은 서드파티 API 요청 인증과 유사하지만, 토큰 발급 방식에 약간의 차이가 있습니다.

API 토큰 발급

모바일 앱 인증을 위해, 사용자의 이메일/아이디, 비밀번호, 기기 이름을 받아 새로운 Sanctum 토큰으로 교환하는 라우트를 만듭니다. "기기 이름"은 참고용 값으로 어떤 값이든 사용할 수 있습니다. 일반적으로 사용자가 알아볼 수 있는 이름(예: "홍길동의 Galaxy S24")을 사용하는 것이 좋습니다.

보통 모바일 앱의 "로그인" 화면에서 이 토큰 엔드포인트에 요청을 보냅니다. 엔드포인트는 평문 API 토큰을 반환하고, 이를 기기에 저장하여 이후 API 요청에 활용합니다:

use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Illuminate\Validation\ValidationException; Route::post('/sanctum/token', function (Request $request) { $request->validate([ 'email' => 'required|email', 'password' => 'required', 'device_name' => 'required', ]); $user = User::where('email', $request->email)->first(); if (! $user || ! Hash::check($request->password, $user->password)) { throw ValidationException::withMessages([ 'email' => ['입력한 인증 정보가 올바르지 않습니다.'], ]); } return $user->createToken($request->device_name)->plainTextToken; });

모바일 앱에서 이 토큰으로 API 요청을 보낼 때는 Authorization 헤더에 Bearer 토큰으로 포함합니다.

NOTE

모바일 애플리케이션에 토큰을 발급할 때도 토큰 권한(abilities)을 자유롭게 지정할 수 있습니다.

라우트 보호

앞서 설명한 것과 동일하게, sanctum 인증 가드를 라우트에 연결하면 모든 요청에 인증을 요구할 수 있습니다:

Route::get('/user', function (Request $request) { return $request->user(); })->middleware('auth:sanctum');

토큰 폐기

사용자가 모바일 기기에 발급된 API 토큰을 폐기할 수 있도록, 웹 애플리케이션의 "계정 설정" 화면에 기기 이름과 함께 "폐기" 버튼을 제공할 수 있습니다. 사용자가 "폐기" 버튼을 클릭하면 데이터베이스에서 해당 토큰을 삭제합니다. HasApiTokens 트레이트의 tokens 관계로 사용자의 API 토큰에 접근할 수 있습니다:

// 모든 토큰 폐기... $user->tokens()->delete(); // 특정 토큰 폐기... $user->tokens()->where('id', $tokenId)->delete();

테스트

테스트 시에는 Sanctum::actingAs 메서드를 사용해 사용자를 인증하고 토큰에 부여할 권한을 지정할 수 있습니다:

Pest

use App\Models\User; use Laravel\Sanctum\Sanctum; test('작업 목록을 가져올 수 있다', function () { Sanctum::actingAs( User::factory()->create(), ['view-tasks'] ); $response = $this->get('/api/task'); $response->assertOk(); });

PHPUnit

use App\Models\User; use Laravel\Sanctum\Sanctum; public function test_task_list_can_be_retrieved(): void { Sanctum::actingAs( User::factory()->create(), ['view-tasks'] ); $response = $this->get('/api/task'); $response->assertOk(); }

토큰에 모든 권한을 부여하려면, actingAs 메서드의 권한 목록에 *를 포함하면 됩니다:

Sanctum::actingAs( User::factory()->create(), ['*'] );

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

번역일: 2026년 6월 27일