> ## 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 解决两大问题：

| 认证模式         | 机制                                | 主要用途                |
| ------------ | --------------------------------- | ------------------- |
| **API 令牌认证** | `Authorization: Bearer <token>` 头 | 移动应用、第三方集成          |
| **SPA 认证**   | Session Cookie + 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 trait

在 `User` 模型上添加 `HasApiTokens` trait。

```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 哈希后的令牌。

### 设置 Scope（能力）

可以为令牌附加能力（scope），限定该令牌能够执行的操作。

```php theme={null}
// 签发带 scope 的令牌
$token = $user->createToken('mobile-app', ['server:update', 'server:read']);

return $token->plainTextToken;
```

处理请求时检查令牌的 scope：

```php theme={null}
if ($request->user()->tokenCan('server:update')) {
    // 执行更新操作
}

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

#### 通过中间件校验 scope

在 `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 认证使用 Session Cookie，无需签发和管理令牌。适合自家前端（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();
})
```

### 配置一方（First-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;
```

别忘了设置 Session Cookie 域名：

```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 Session

    SPA->>API: GET /sanctum/csrf-cookie
    API-->>SPA: 设置 XSRF-TOKEN Cookie

    SPA->>API: POST /login<br>携带 X-XSRF-TOKEN 头
    API->>Session: 创建 Session
    API-->>SPA: Set-Cookie: laravel_session

    SPA->>API: GET /api/user<br>Cookie: laravel_session
    API->>Session: 校验 Session
    Session-->>API: 已认证用户
    API-->>SPA: 用户信息
```

<Steps>
  <Step title="获取 CSRF Cookie">
    登录前访问 `/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="发送已认证请求">
    登录后的请求会通过 Session Cookie 自动认证。

    ```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` trait：

    ```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;

    // 签发带 scope 的令牌
    $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 工具等无 Session 的客户端。
    * **SPA 认证**：适用于自家 Vue / React / Next.js 前端等同一域名（或子域名）上的 SPA。更安全，无需管理令牌。
  </Accordion>
</AccordionGroup>


## Related topics

- [用 Laravel 构建 MCP 服务器](/zh/advanced/mcp-server.md)
- [Laravel Passport（OAuth2 服务器实现）](/zh/passport.md)
- [路由](/zh/routing.md)
- [HTTP 客户端](/zh/http-client.md)
- [Service Account 认证 - Google Sheets API for Laravel](/zh/packages/laravel-google-sheets/service-account.md)
