Laravel Sanctum
번역일: 2026년 6월 27일
Laravel Sanctum
소개
Laravel Sanctum은 SPA(싱글 페이지 애플리케이션), 모바일 앱, 그리고 단순한 토큰 기반 API를 위한 경량 인증 시스템입니다. Sanctum을 사용하면 사용자 계정마다 여러 개의 API 토큰을 발급할 수 있으며, 각 토큰에는 허용할 작업을 지정하는 능력(abilities/scopes)을 부여할 수 있습니다.
동작 원리
Sanctum은 서로 다른 두 가지 문제를 해결하기 위해 만들어졌습니다. 각각을 먼저 살펴보겠습니다.
API 토큰
첫 번째로, Sanctum은 OAuth의 복잡함 없이 사용자에게 API 토큰을 발급할 수 있는 간단한 패키지입니다. GitHub 등에서 제공하는 "개인 액세스 토큰"에서 영감을 받았습니다. 예를 들어, 앱의 "계정 설정" 화면에서 사용자가 자신의 API 토큰을 직접 생성할 수 있도록 만들고 싶다면 Sanctum으로 간단히 구현할 수 있습니다. 이 토큰은 일반적으로 유효 기간이 매우 길지만(몇 년 단위), 사용자가 언제든지 직접 폐기할 수 있습니다.
Sanctum은 API 토큰을 단일 데이터베이스 테이블에 저장하고, 유효한 API 토큰이 담긴 Authorization 헤더를 통해 들어오는 HTTP 요청을 인증합니다.
SPA 인증
두 번째로, Sanctum은 Laravel API와 통신해야 하는 SPA를 간단하게 인증할 수 있는 방법을 제공합니다. 이 SPA는 Laravel 애플리케이션과 같은 저장소에 있을 수도 있고, Next.js나 Nuxt.js로 만들어진 별도의 저장소일 수도 있습니다.
이 기능에서 Sanctum은 토큰을 전혀 사용하지 않습니다. 대신 Laravel에 내장된 쿠키 기반 세션 인증 서비스를 활용합니다. 보통 Laravel의 web 인증 가드를 통해 이를 처리하며, 그 결과 CSRF 보호, 세션 인증, XSS로 인한 인증 정보 유출 방지 등의 이점을 모두 누릴 수 있습니다.
Sanctum은 요청이 자체 SPA 프런트엔드에서 발생한 경우에만 쿠키 인증을 시도합니다. 들어오는 HTTP 요청을 검사할 때 먼저 인증 쿠키를 확인하고, 쿠키가 없으면 Authorization 헤더에서 유효한 API 토큰을 찾습니다.
NOTE
Sanctum을 API 토큰 인증 전용으로만 사용하거나, SPA 인증 전용으로만 사용해도 됩니다. Sanctum을 도입했다고 해서 두 기능을 모두 사용해야 하는 것은 아닙니다.
설치
install:api Artisan 명령어로 Laravel Sanctum을 설치할 수 있습니다:
php artisan install:apiSPA 인증에 Sanctum을 활용할 계획이라면 이 문서의 SPA 인증 섹션을 참고하세요.
설정
기본 모델 재정의
일반적으로 필요하지 않지만, Sanctum이 내부적으로 사용하는 PersonalAccessToken 모델을 자유롭게 확장할 수 있습니다:
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
class PersonalAccessToken extends SanctumPersonalAccessToken
{
// ...
}커스텀 모델을 만들었다면, AppServiceProvider의 boot 메서드에서 usePersonalAccessTokenModel을 호출해 Sanctum에 알려주세요:
use App\Models\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}API 토큰 인증
NOTE
자체 SPA를 인증할 때는 API 토큰을 사용하지 마세요. 대신 Sanctum의 내장 SPA 인증 기능을 사용하세요.
API 토큰 발급
Sanctum을 통해 API 요청 인증에 사용할 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;들어오는 요청을 처리할 때 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): void {
$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에서 시작된 요청
편의를 위해, 인증된 요청이 자체 SPA 프런트엔드에서 발생했고 Sanctum의 내장 SPA 인증을 사용하고 있다면, tokenCan 메서드는 항상 true를 반환합니다.
그렇다고 사용자가 모든 작업을 수행할 수 있다는 의미는 아닙니다. 실제 허용 여부는 애플리케이션의 인가 정책(authorization policies)에서 결정합니다. 정책은 토큰에 해당 능력이 부여되어 있는지, 그리고 사용자 자신이 그 작업을 수행할 수 있는지를 함께 검사합니다.
예를 들어, 서버를 관리하는 애플리케이션이라면 다음처럼 토큰 권한과 소유권을 동시에 검사할 수 있습니다:
return $request->user()->id === $server->user_id &&
$request->user()->tokenCan('server:update')자체 UI 요청에서 tokenCan이 항상 true를 반환하는 것이 처음에는 어색하게 느껴질 수 있습니다. 하지만 이 방식 덕분에 요청이 UI에서 온 것인지, 서드파티 API 소비자로부터 온 것인지 구분하지 않고 인가 정책 내에서 항상 tokenCan을 일관되게 호출할 수 있습니다.
라우트 보호
모든 요청에 인증을 요구하려면, routes/web.php 및 routes/api.php의 보호할 라우트에 sanctum 인증 가드를 지정하세요. 이 가드는 상태 유지(쿠키 기반) 요청이거나 서드파티의 유효한 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()->plus(weeks: 1)
)->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.js로 구성된 완전히 분리된 저장소일 수도 있습니다.
이 기능에서는 토큰을 사용하지 않습니다. 대신 Laravel의 쿠키 기반 세션 인증 서비스를 사용하므로 CSRF 보호, 세션 인증, XSS로 인한 인증 정보 유출 방지 효과를 모두 누릴 수 있습니다.
WARNING
SPA와 API는 동일한 최상위 도메인을 공유해야 합니다. 단, 서로 다른 서브도메인에 위치하는 것은 가능합니다. 또한 요청 시 Accept: application/json 헤더와 함께 Referer 또는 Origin 헤더를 반드시 포함해야 합니다.
설정
자체 도메인 설정
먼저, SPA가 요청을 보낼 도메인을 설정해야 합니다. sanctum 설정 파일의 stateful 옵션에 해당 도메인을 지정하세요. 이 설정은 API에 요청할 때 Laravel 세션 쿠키를 통해 "상태 유지(stateful)" 인증을 유지할 도메인을 결정합니다.
Sanctum은 자체 도메인 설정을 돕는 두 가지 헬퍼 함수를 제공합니다. Sanctum::currentApplicationUrlWithPort()는 APP_URL 환경 변수에서 현재 앱 URL을 반환하고, Sanctum::currentRequestHost()는 런타임에 현재 요청의 호스트로 치환되는 플레이스홀더를 stateful 도메인 목록에 삽입합니다.
WARNING
포트가 포함된 URL(예: 127.0.0.1:8000)로 앱에 접근하는 경우, 도메인에 포트 번호를 반드시 함께 포함해야 합니다.
Sanctum 미들웨어
다음으로, SPA에서 들어오는 요청은 세션 쿠키로 인증하면서 서드파티나 모바일 앱의 요청은 API 토큰으로 인증할 수 있도록 설정해야 합니다. bootstrap/app.php 파일에서 statefulApi 미들웨어 메서드를 호출하면 됩니다:
->withMiddleware(function (Middleware $middleware): void {
$middleware->statefulApi();
})CORS와 쿠키
서로 다른 서브도메인에서 실행 중인 SPA의 인증에 문제가 있다면, CORS(교차 출처 리소스 공유) 또는 세션 쿠키 설정이 잘못되었을 가능성이 높습니다.
config/cors.php 설정 파일은 기본적으로 생성되지 않습니다. CORS 옵션을 직접 설정하려면 config:publish Artisan 명령어로 설정 파일을 내보내세요:
php artisan config:publish cors그런 다음, CORS 설정에서 Access-Control-Allow-Credentials 헤더가 True로 반환되도록 config/cors.php의 supports_credentials 옵션을 true로 설정하세요.
또한 axios의 전역 인스턴스에서 withCredentials와 withXSRFToken 옵션을 활성화해야 합니다. 보통 resources/js/bootstrap.js 파일에서 설정합니다. axios 대신 다른 HTTP 클라이언트를 사용하는 경우에도 동일한 설정을 적용하세요:
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;마지막으로, 세션 쿠키 도메인 설정이 루트 도메인의 모든 서브도메인을 지원하도록 config/session.php에서 도메인 앞에 .을 붙여주세요:
'domain' => '.domain.com',인증 처리
CSRF 보호
SPA 로그인 전, CSRF 보호를 초기화하기 위해 /sanctum/csrf-cookie 엔드포인트에 먼저 요청을 보내야 합니다:
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.php의 withRouting 메서드에서 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 인스턴스를 사용할 수 있습니다:
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 S25")을 사용하는 것이 좋습니다.
모바일 앱의 "로그인" 화면에서 이 토큰 엔드포인트로 요청하면, 반환된 평문 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');토큰 폐기
웹 앱의 "계정 설정" UI에서 기기별 토큰 목록을 보여주고 "폐기" 버튼을 제공하면 사용자가 직접 모바일 기기의 API 토큰을 관리할 수 있습니다. "폐기" 버튼을 클릭하면 해당 토큰을 데이터베이스에서 삭제하세요. HasApiTokens 트레이트의 tokens 관계로 사용자의 토큰에 접근할 수 있습니다:
// 모든 토큰 폐기...
$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(),
['*']
);