> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel Sanctum (API 토큰 인증)

> Laravel Sanctum에 의한 API 토큰 인증과 SPA 인증의 구현 방법을 설명합니다. 심플하고 경량인 인증 패키지의 도입부터 실천적인 사용법까지.

## Sanctum이란

Laravel Sanctum은 SPA(싱글 페이지 애플리케이션)·모바일 앱·심플한 API를 위한 경량 인증 패키지입니다. 복잡한 OAuth 지식이 없어도, 사용자별로 여러 API 토큰을 발행·관리할 수 있습니다.

Sanctum이 해결하는 문제는 2가지입니다.

| 인증 모드         | 구조                                 | 주요 용도                  |
| ------------- | ---------------------------------- | ---------------------- |
| **API 토큰 인증** | `Authorization: Bearer <token>` 헤더 | 모바일 앱·서드파티 연계          |
| **SPA 인증**    | 세션 쿠키 + CSRF 보호                    | 자사 프론트엔드 (Vue/React 등) |

<Info>
  자사 SPA로부터 API를 호출하는 경우는 SPA 인증을 사용합니다. 모바일 앱이나 서드파티가 API를 이용하는 경우는 API 토큰 인증을 사용합니다. 어느 한쪽만을 사용하는 것도 상관없습니다.
</Info>

### Passport와의 구분 사용

|            | Sanctum      | Passport           |
| ---------- | ------------ | ------------------ |
| **복잡함**    | 심플           | 풀 기능 OAuth2        |
| **적합한 용도** | 자사 SPA·모바일 앱 | 외부 앱에의 OAuth 프로바이더 |
| **토큰 종별**  | 퍼스널 액세스 토큰   | OAuth2 액세스 토큰      |

외부 서비스에 대해 OAuth2 프로바이더가 될 필요가 있는 경우는 Passport를 선택하지만, 많은 애플리케이션에서는 Sanctum으로 충분합니다.

***

## 설치와 설정

### 설치

`install:api` Artisan 명령을 실행하기만 하면 Sanctum이 셋업됩니다.

```shell theme={null}
php artisan install:api
```

이 명령은 이하를 자동적으로 수행합니다.

* `laravel/sanctum` 패키지의 설치
* `personal_access_tokens` 테이블의 마이그레이션 파일의 공개
* 마이그레이션의 실행

### HasApiTokens 트레이트를 추가

`User` 모델에 `HasApiTokens` 트레이트를 추가합니다.

```php theme={null}
// app/Models/User.php

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}
```

이로써 `$user->createToken()`이나 `$user->tokens` 등의 메서드를 사용할 수 있게 됩니다.

***

## API 토큰 인증

### 토큰 플로우

```mermaid theme={null}
sequenceDiagram
    participant Client as 클라이언트
    participant API as Laravel API
    participant DB as 데이터베이스

    Client->>API: POST /login (email, password)
    API->>DB: 사용자 인증
    DB-->>API: 사용자 정보
    API->>DB: 토큰 생성·보존(해시)
    API-->>Client: plainTextToken을 반환

    Note over Client: 토큰을 보존

    Client->>API: GET /api/user<br>Authorization: Bearer <token>
    API->>DB: 토큰 대조(SHA-256)
    DB-->>API: 인증 완료 사용자
    API-->>Client: 응답
```

### 토큰을 발행

`createToken()` 메서드로 토큰을 발행합니다. `plainTextToken` 프로퍼티에서 평문의 토큰 값을 취득할 수 있습니다. **평문 토큰은 데이터베이스에 보존되지 않기** 때문에, 발행 직후에 사용자에게 반환할 필요가 있습니다.

```php theme={null}
use Illuminate\Http\Request;

Route::post('/tokens/create', function (Request $request) {
    $token = $request->user()->createToken($request->token_name);

    return ['token' => $token->plainTextToken];
})->middleware('auth');
```

데이터베이스에는 SHA-256으로 해시화된 토큰이 보존됩니다.

### 스코프(어빌리티)를 설정

토큰에 대해 어빌리티(스코프)를 부여함으로써, 그 토큰으로 실행할 수 있는 조작을 제한할 수 있습니다.

```php theme={null}
// 스코프 첨부 토큰을 발행
$token = $user->createToken('mobile-app', ['server:update', 'server:read']);

return $token->plainTextToken;
```

리퀘스트 처리 시에 토큰의 스코프를 확인합니다.

```php theme={null}
if ($request->user()->tokenCan('server:update')) {
    // 업데이트 조작을 실행
}

if ($request->user()->tokenCant('server:update')) {
    abort(403);
}
```

#### 미들웨어에서 스코프를 확인

`bootstrap/app.php`에 미들웨어 에일리어스를 등록합니다.

```php theme={null}
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, // 어느 하나의 어빌리티를 가짐
    ]);
})
```

라우트에 미들웨어를 적용합니다.

```php theme={null}
// check-status와 place-orders의 양쪽을 가진 토큰만 허가
Route::get('/orders', function () {
    // ...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);

// check-status 또는 place-orders 중 어느 하나를 가진 토큰을 허가
Route::get('/orders', function () {
    // ...
})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);
```

### 토큰의 유효 기한

기본적으로 Sanctum 토큰에 유효 기한은 없습니다. `config/sanctum.php`의 `expiration` 옵션에서 분 단위의 유효 기한을 설정할 수 있습니다.

```php theme={null}
// config/sanctum.php
'expiration' => 525600, // 365일(분)
```

토큰별로 유효 기한을 지정할 수도 있습니다.

```php theme={null}
$token = $user->createToken(
    'token-name',
    ['*'],
    now()->addWeeks(1) // 1주 후에 기한 만료
)->plainTextToken;
```

유효 기한을 설정하고 있는 경우는, 기한 만료 토큰을 정기적으로 삭제하는 스케줄을 설정합니다.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=24')->daily();
```

### 토큰을 실효시키기

```php theme={null}
// 전 토큰을 삭제
$user->tokens()->delete();

// 현재의 리퀘스트에서 사용되고 있는 토큰을 삭제
$request->user()->currentAccessToken()->delete();

// 특정의 토큰을 삭제
$user->tokens()->where('id', $tokenId)->delete();
```

***

## SPA 인증

SPA 인증은 세션 쿠키를 사용하기 때문에, 토큰을 발행·관리할 필요가 없습니다. 자사 프론트엔드(Vue, React, Next.js 등)로부터 API를 호출하는 경우에 적합합니다.

<Warning>
  SPA 인증을 사용하려면 SPA와 API가 같은 최상위 도메인을 공유하고 있을 필요가 있습니다(서브도메인은 달라도 상관없습니다). 또한 리퀘스트에 `Accept: application/json` 헤더와 `Referer` 또는 `Origin` 헤더를 포함할 필요가 있습니다.
</Warning>

### Sanctum 미들웨어를 활성화

`bootstrap/app.php`에서 `statefulApi()` 미들웨어를 활성화합니다.

```php theme={null}
->withMiddleware(function (Middleware $middleware): void {
    $middleware->statefulApi();
})
```

### 1st-party 도메인을 설정

`config/sanctum.php`의 `stateful` 옵션에 SPA의 도메인을 설정합니다.

```php theme={null}
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
    '%s%s',
    'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
    Sanctum::currentApplicationUrlWithPort()
))),
```

### CORS 설정

다른 서브도메인에서 API를 호출하는 경우는, CORS 설정이 필요합니다.

```shell theme={null}
php artisan config:publish cors
```

`config/cors.php`에서 `supports_credentials`를 `true`로 설정합니다.

```php theme={null}
// config/cors.php
'supports_credentials' => true,
```

프론트엔드의 axios에도 설정이 필요합니다.

```js theme={null}
// resources/js/bootstrap.js
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
```

세션 쿠키의 도메인 설정도 잊지 말고 수행합니다.

```php theme={null}
// config/session.php
'domain' => '.example.com', // 선두에 점을 붙임
```

### SPA로부터의 인증 플로우

```mermaid theme={null}
sequenceDiagram
    participant SPA as SPA 프론트엔드
    participant API as Laravel API
    participant Session as 세션

    SPA->>API: GET /sanctum/csrf-cookie
    API-->>SPA: XSRF-TOKEN 쿠키를 세팅

    SPA->>API: POST /login<br>X-XSRF-TOKEN 헤더
    API->>Session: 세션 작성
    API-->>SPA: Set-Cookie: laravel_session

    SPA->>API: GET /api/user<br>Cookie: laravel_session
    API->>Session: 세션 확인
    Session-->>API: 인증 완료 사용자
    API-->>SPA: 사용자 정보
```

<Steps>
  <Step title="CSRF 쿠키를 취득">
    로그인 전에 `/sanctum/csrf-cookie` 엔드포인트를 호출해 CSRF 보호를 초기화합니다.

    ```js theme={null}
    await axios.get('/sanctum/csrf-cookie');
    ```
  </Step>

  <Step title="로그인 리퀘스트를 송신">
    `/login` 엔드포인트에 POST 리퀘스트를 보냅니다.

    ```js theme={null}
    await axios.post('/login', {
        email: 'user@example.com',
        password: 'password',
    });
    ```
  </Step>

  <Step title="인증 완료 리퀘스트를 보냄">
    로그인 후의 리퀘스트는 세션 쿠키로 자동적으로 인증됩니다.

    ```js theme={null}
    const response = await axios.get('/api/user');
    console.log(response.data); // 인증 완료 사용자 정보
    ```
  </Step>
</Steps>

***

## 인증 완료 라우트의 보호

`auth:sanctum` 미들웨어를 라우트에 적용하면, 미인증의 리퀘스트에 대해 `401 Unauthorized`가 반환됩니다. API 토큰 인증·SPA 인증의 양쪽을 이 미들웨어 하나로 처리할 수 있습니다.

```php theme={null}
use Illuminate\Http\Request;

// 단일 라우트를 보호
Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

// 라우트 그룹을 보호
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/profile', [ProfileController::class, 'show']);
    Route::put('/profile', [ProfileController::class, 'update']);
    Route::get('/posts', [PostController::class, 'index']);
});
```

***

## 실용 예: 로그인 API와 토큰 반환

모바일 앱을 위한 API 토큰 인증을 구현하는 예입니다.

<Steps>
  <Step title="로그인 엔드포인트를 작성">
    ```php theme={null}
    // routes/api.php

    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', 'string'],
        ]);

        $user = User::where('email', $request->email)->first();

        if (! $user || ! Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['제공된 인증 정보가 올바르지 않습니다.'],
            ]);
        }

        return response()->json([
            'token' => $user->createToken($request->device_name)->plainTextToken,
        ]);
    });
    ```
  </Step>

  <Step title="인증 완료 라우트를 작성">
    ```php theme={null}
    // routes/api.php

    Route::middleware('auth:sanctum')->group(function () {
        // 현재 사용자 정보를 반환
        Route::get('/user', function (Request $request) {
            return $request->user();
        });

        // 현재의 토큰을 실효시켜 로그아웃
        Route::post('/logout', function (Request $request) {
            $request->user()->currentAccessToken()->delete();

            return response()->json(['message' => '로그아웃했습니다.']);
        });

        // 전 디바이스에서 로그아웃
        Route::post('/logout/all', function (Request $request) {
            $request->user()->tokens()->delete();

            return response()->json(['message' => '전 디바이스에서 로그아웃했습니다.']);
        });
    });
    ```
  </Step>

  <Step title="클라이언트로부터 리퀘스트를 보냄">
    ```js theme={null}
    // 로그인
    const { data } = await axios.post('/api/sanctum/token', {
        email: 'user@example.com',
        password: 'password',
        device_name: 'My iPhone',
    });

    const token = data.token;

    // 인증 완료 리퀘스트
    const response = await axios.get('/api/user', {
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });
    ```
  </Step>
</Steps>

***

## 테스트

Sanctum의 테스트에서는 `Sanctum::actingAs()`를 사용해 사용자를 인증하고, 부여할 어빌리티를 지정합니다.

<Tabs>
  <Tab title="Pest">
    ```php theme={null}
    use App\Models\User;
    use Laravel\Sanctum\Sanctum;

    test('태스크 일람을 취득할 수 있다', function () {
        Sanctum::actingAs(
            User::factory()->create(),
            ['view-tasks']
        );

        $response = $this->get('/api/tasks');

        $response->assertOk();
    });

    test('전 어빌리티를 가진 토큰으로 접근할 수 있다', function () {
        Sanctum::actingAs(
            User::factory()->create(),
            ['*']
        );

        $response = $this->get('/api/tasks');

        $response->assertOk();
    });
    ```
  </Tab>

  <Tab title="PHPUnit">
    ```php theme={null}
    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/tasks');

        $response->assertOk();
    }
    ```
  </Tab>
</Tabs>

***

## 정리

<AccordionGroup>
  <Accordion title="설치 순서의 확인">
    ```shell theme={null}
    # Sanctum을 설치하여 마이그레이션을 실행
    php artisan install:api
    ```

    User 모델에 `HasApiTokens` 트레이트를 추가:

    ```php theme={null}
    use Laravel\Sanctum\HasApiTokens;

    class User extends Authenticatable
    {
        use HasApiTokens, HasFactory, Notifiable;
    }
    ```
  </Accordion>

  <Accordion title="자주 사용하는 API 정리">
    ```php theme={null}
    // 토큰 발행
    $token = $user->createToken('token-name')->plainTextToken;

    // 스코프 첨부 토큰 발행
    $token = $user->createToken('token-name', ['read', 'write'])->plainTextToken;

    // 스코프 확인
    $user->tokenCan('read');   // true/false
    $user->tokenCant('write'); // true/false

    // 토큰 실효
    $user->tokens()->delete();                        // 전 토큰
    $request->user()->currentAccessToken()->delete(); // 현재의 토큰
    $user->tokens()->where('id', $id)->delete();      // 특정의 토큰
    ```
  </Accordion>

  <Accordion title="API 토큰 인증 vs SPA 인증의 선택">
    * **API 토큰 인증**: 모바일 앱, 서드파티, CLI 도구 등, 세션을 가지지 않는 클라이언트로부터 이용하는 경우.
    * **SPA 인증**: 자사의 Vue/React/Next.js 프론트엔드 등, 같은 도메인(또는 서브도메인) 위의 SPA로부터 이용하는 경우. 보다 시큐어하고 토큰 관리가 불필요.
  </Accordion>
</AccordionGroup>


## Related topics

- [OAuth 2.0 인증 - Google Sheets API for Laravel](/ko/packages/laravel-google-sheets/oauth.md)
- [커스텀 인증 가드 구현](/ko/advanced/custom-auth-guard.md)
- [Service account 인증 - Google Sheets API for Laravel](/ko/packages/laravel-google-sheets/service-account.md)
- [Laravel에서 MCP 서버 만들기](/ko/advanced/mcp-server.md)
- [Laravel Passport(OAuth2 서버 구현)](/ko/passport.md)
