Laravel Sanctumの使い方:インストールからAPIトークン認証・SPA認証まで徹底解説

実装・応用テクニック

Laravel Sanctum(サンクタム)は、SPA(シングルページアプリケーション)・モバイルアプリ・シンプルなAPIトークン認証を、最小限の設定で実現できるLaravel公式の軽量認証パッケージです。「昔の記事にあるapp/Http/Kernel.phpを編集する手順が見つからない」「SPA認証とAPIトークン認証の違いや使い分けがよくわからない」といった疑問を持つ開発者も多い分野です。本記事では、Laravel 11 / 12 / 13に対応した最新の導入手順から、APIトークン認証(アビリティ管理含む)、SPA(Cookieベース)認証の実装手順、Laravel Passportとの使い分け、つまずきやすいエラーのトラブルシューティングまで実機検証をもとに徹底解説します。

  1. Laravel Sanctumとは?【概要と特徴】
  2. Laravel SanctumとLaravel Passportの違い・選び方
    1. どちらを選ぶべきか?判断の基準
  3. 動作要件とLaravelバージョンの前提
  4. Laravel Sanctumのインストール手順
    1. 1. install:apiコマンドによる一括セットアップ(推奨)
    2. 2. 既存プロジェクトへの手動インストール
    3. 3. UserモデルにHasApiTokensトレイトを追加
  5. 【方式1】APIトークン認証の実装と使い方(モバイル・外部連携向け)
    1. 1. ルーティングの定義(routes/api.php)
    2. 2. 認証コントローラー(AuthController)の実装
    3. 3. トークンのアビリティ(権限スコープ)機能
    4. 4. curlコマンドによる動作確認
  6. 【方式2】SPA認証(Cookieベース)の実装と設定手順(Vue/React/Next.js向け)
    1. なぜSPAではCookie認証を使うべきなのか?
    2. 1. バックエンドの設定(Laravel 11 / 12 / 13)
    3. 2. フロントエンドの認証フローと実装例
  7. トークンの有効期限と定期メンテナンス
    1. 有効期限の設定(config/sanctum.php)
    2. 期限切れトークンの自動削除(sanctum:prune-expired)
  8. Sanctum認証のFeatureテスト(自動テスト)
  9. よくあるエラー・トラブルシューティング
  10. まとめ
  11. よくある質問(FAQ)
    1. Q. Laravel SanctumとLaravel Passportはどちらを選ぶべきですか?
    2. Q. 1つのLaravelアプリでSPA認証とAPIトークン認証を同時に併用できますか?
    3. Q. SPA認証でログイン後にセッションが切れる場合は何を確認すべきですか?
  12. 関連記事

Laravel Sanctumとは?【概要と特徴】

Laravel Sanctumは、現代のWeb開発で必要とされるAPI認証をシンプルかつ安全に実装するための公式ライブラリです。OAuth2のような複雑なプロトコルを導入することなく、次の2種類の認証方式を提供します。

  • APIトークン認証:モバイルアプリ、デスクトップアプリ、外部サービス連携向けに、ユーザーごとにアクセストークン(Personal Access Token)を発行してAPIアクセスを許可する方式(Bearerトークン)
  • SPA認証(Cookie/セッションベース):同一トップレベルドメイン上のVue、React、Next.js、NuxtなどのSPAから、Laravel標準のセッションCookieとCSRF保護を使って安全に認証する方式

フル機能のOAuth2サーバーを構築することなく、実務で必要となる9割以上の認証要件を軽量にカバーできる点が最大の魅力です。

Laravel SanctumとLaravel Passportの違い・選び方

Laravel公式にはSanctumのほかに「Laravel Passport」という認証パッケージも存在します。用途や要件によってどちらを選ぶべきかが明確に分かれています。

比較項目 Laravel Sanctum Laravel Passport
認証方式 APIトークン(Personal Access Token) / CookieベースSPA認証 OAuth2(認可コードグラント、PKCE、クライアントクレデンシャルなど)
主な用途 自社SPA、自社モバイルアプリ、シンプルな外部連携API サードパーティへのAPI公開、OAuth2認可サーバーの構築
導入の難易度 非常に簡単(install:apiコマンド1つでセットアップ可能) 中〜高(暗号鍵の生成やOAuth2クライアント管理が必要)
SPA認証 標準対応(CookieとCSRF保護による安全なステートフル認証) 基本的にトークンベース(Cookie連携には追加設定が必要)
トークン権限管理 Abilities(アビリティによるシンプルな権限設定) OAuth2 Scopes(スコープ管理)

どちらを選ぶべきか?判断の基準

  • Sanctumを選ぶべきケース:自社サービス用のSPA(React/Vue/Next.jsなど)や自社モバイルアプリのバックエンドAPIを構築する場合。シンプルなAPIキーやパーソナルアクセストークンで十分な場合。
  • Passportを選ぶべきケース:「Googleでログイン」や「GitHub連携」のように、第三者のアプリケーションに対して認可画面を出してAPIアクセスを許可するOAuth2認可サーバーを作りたい場合。Laravel Passportの使い方完全ガイドもあわせて参照してください。

動作要件とLaravelバージョンの前提

  • PHP 8.2以上
  • Laravel 11.x / 12.x / 13.x
  • Composerが利用可能な環境

※本記事はLaravel 11以降(Laravel 11 / 12 / 13)の標準構成に基づいています。Laravel 11以降ではapp/Http/Kernel.phpが廃止され、ミドルウェア設定はbootstrap/app.phpに統合されています。古いドキュメントの「Kernel.phpを編集する」という記述は現行バージョンでは該当しないためご注意ください。

Laravel Sanctumのインストール手順

1. install:apiコマンドによる一括セットアップ(推奨)

Laravel 11以降でAPIを新規作成する場合、install:apiコマンドを実行するのが最も確実です。

php artisan install:api

このコマンドを実行すると、以下の処理が自動で行われます。

  1. laravel/sanctumパッケージのインストール
  2. routes/api.phpの生成(認証ルートの雛形付き)
  3. bootstrap/app.phpへのAPIルーティング定義の自動登録
  4. トークンを保存するpersonal_access_tokensテーブルのマイグレーション自動実行

2. 既存プロジェクトへの手動インストール

すでにroutes/api.phpが存在する場合や個別に追加したい場合は、以下の手順で手動導入できます。

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

3. UserモデルにHasApiTokensトレイトを追加

トークンを発行・検証する対象のユーザーモデル(通常はapp/Models/User.php)に、Laravel\Sanctum\HasApiTokensトレイトを追加します。この設定は自動では行われないため手動での追加が必須です。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

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

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];
}

【方式1】APIトークン認証の実装と使い方(モバイル・外部連携向け)

モバイルアプリやサードパーティからのリクエストを受け付ける「APIトークン認証」の実装手順を解説します。

1. ルーティングの定義(routes/api.php)

認証不要な公開ルート(ユーザー登録・ログイン)と、auth:sanctumミドルウェアで保護されたルート(ユーザー情報取得・ログアウト)を定義します。Laravel API Route設定方法も参考にしてください。

<?php

use App\Http\Controllers\Api\AuthController;
use Illuminate\Support\Facades\Route;

// 公開エンドポイント
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

// 認証必須エンドポイント
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', [AuthController::class, 'user']);
    Route::post('/logout', [AuthController::class, 'logout']);
});

2. 認証コントローラー(AuthController)の実装

コントローラーを作成します。

php artisan make:controller Api/AuthController

app/Http/Controllers/Api/AuthController.phpに以下の処理を記述します。

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    /**
     * ユーザー新規登録&トークン発行
     */
    public function register(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8',
        ]);

        $user = User::create([
            'name' => $validated['name'],
            'email' => $validated['email'],
            'password' => Hash::make($validated['password']),
        ]);

        // トークンの発行(abilitiesを指定することも可能)
        $token = $user->createToken('auth_token', ['*'])->plainTextToken;

        return response()->json([
            'message' => 'User registered successfully',
            'user' => $user,
            'access_token' => $token,
            'token_type' => 'Bearer',
        ], 201);
    }

    /**
     * ログイン認証&トークン発行
     */
    public function login(Request $request): JsonResponse
    {
        $request->validate([
            'email' => 'required|string|email',
            'password' => 'required|string',
        ]);

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

        if (! $user || ! Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['メールアドレスまたはパスワードが正しくありません。'],
            ]);
        }

        // トークンを発行
        $token = $user->createToken('auth_token', ['*'])->plainTextToken;

        return response()->json([
            'message' => 'Logged in successfully',
            'access_token' => $token,
            'token_type' => 'Bearer',
        ]);
    }

    /**
     * 認証中ユーザー情報の取得
     */
    public function user(Request $request): JsonResponse
    {
        return response()->json($request->user());
    }

    /**
     * ログアウト(現在のトークンを削除)
     */
    public function logout(Request $request): JsonResponse
    {
        // リクエストに使用されたトークンのみを削除
        $request->user()->currentAccessToken()->delete();

        // ユーザーの全トークンを一括失効させたい場合は以下を使用:
        // $request->user()->tokens()->delete();

        return response()->json([
            'message' => 'Logged out successfully',
        ]);
    }
}

3. トークンのアビリティ(権限スコープ)機能

Sanctumでは、トークンごとに細かな操作権限(Abilities)を付与できます。

// 'post:create' と 'post:read' の権限のみを持つトークンを発行
$token = $user->createToken('editor-token', ['post:create', 'post:read'])->plainTextToken;

コントローラー内やルートミドルウェアで権限を検証できます。

// コントローラー内で確認
if ($request->user()->tokenCan('post:create')) {
    // 投稿作成処理
}

// routes/api.php でミドルウェアによる制御(Sanctum組み込み)
Route::middleware(['auth:sanctum', 'ability:post:create'])->post('/posts', [PostController::class, 'store']);

4. curlコマンドによる動作確認

ローカルサーバー(php artisan serve)を起動し、curlで一連の認証フローをテストします。

① ユーザー登録リクエスト

curl -X POST http://127.0.0.1:8000/api/register \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"name":"Taro","email":"taro@example.com","password":"password123"}'

レスポンス(平文トークンが返却される):

{
    "message": "User registered successfully",
    "user": {"id": 1, "name": "Taro", "email": "taro@example.com"},
    "access_token": "1|NaPHWgtD7GWINuzzH0DXGsobieSZERwZvIeBNshK8d1a6608",
    "token_type": "Bearer"
}

② トークンなしで保護ルートにアクセス(401 Unauthenticated)

curl http://127.0.0.1:8000/api/user -H "Accept: application/json"
{"message": "Unauthenticated."}

③ 発行したBearerトークンを付与してアクセス(認証成功)

curl http://127.0.0.1:8000/api/user \
     -H "Accept: application/json" \
     -H "Authorization: Bearer 1|NaPHWgtD7GWINuzzH0DXGsobieSZERwZvIeBNshK8d1a6608"
{
    "id": 1,
    "name": "Taro",
    "email": "taro@example.com"
}

④ ログアウト(トークン削除)

curl -X POST http://127.0.0.1:8000/api/logout \
     -H "Accept: application/json" \
     -H "Authorization: Bearer 1|NaPHWgtD7GWINuzzH0DXGsobieSZERwZvIeBNshK8d1a6608"

ログアウト後、同じトークンで再度/api/userにアクセスすると401 Unauthenticatedとなり、トークンが即座に無効化されたことが確認できます。

【方式2】SPA認証(Cookieベース)の実装と設定手順(Vue/React/Next.js向け)

自社で開発するSPA(Vue.js、React、Next.js、Nuxtなど)とLaravel APIを連携させる場合、APIトークンをlocalStorageに保存する方式ではなく、CookieベースのSPA認証を採用することが強く推奨されます。

なぜSPAではCookie認証を使うべきなのか?

  • XSS攻撃への耐性:トークンをJavaScript(localStorage等)で保持すると、悪意あるスクリプトからトークンを盗まれる危険があります。Cookie(HttpOnly / SameSite)ベースであればJavaScriptから読み取られないため安全です。
  • CSRF保護の自動化:Laravel標準のCSRF対策(XSRF-TOKEN Cookie)がシームレスに機能します。
  • セッション管理の統一:通常のWeb認証(セッション認証)と同じガードやタイムアウト設定を活用できます。Laravel Sessionの使い方も参考にしてください。

1. バックエンドの設定(Laravel 11 / 12 / 13)

① bootstrap/app.php の設定

APIルートに対してステートフル(セッション維持)なミドルウェアを有効化するため、bootstrap/app.phpstatefulApi()を呼び出します。

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        // SPA認証用のステートフルAPIミドルウェアを有効化
        $middleware->statefulApi();
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();

② .env のドメイン設定

フロントエンドSPAのドメインとセッションドメインを環境変数に設定します。

# SPAのドメイン(カンマ区切りで複数指定可能、ポート番号も含める)
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000

# セッションを共有する親ドメイン(ローカル開発時は localhost または .localhost)
SESSION_DOMAIN=localhost

③ CORS設定(config/cors.php)

Cookieの送受信を許可するため、supports_credentialstrueにし、SPAのオリジンを許可します。(config/cors.phpがない場合はphp artisan config:publish corsで作成)

return [
    'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'logout'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['http://localhost:3000'],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => true, // 必ず true に設定
];

2. フロントエンドの認証フローと実装例

SPA認証の通信フローは以下の3ステップで行われます。

  1. GET /sanctum/csrf-cookie にリクエストして、CSRF保護用Cookie(XSRF-TOKEN)をブラウザにセットさせる。
  2. POST /login に認証情報(メールアドレス・パスワード)をPOSTする(Laravel標準のセッションCookieが発行される)。
  3. 以降の GET /api/user やAPIリクエストはブラウザが自動的にCookieを送信することで認証される。

Axiosでの実装コード例:

import axios from 'axios';

// Axiosの共通設定
const apiClient = axios.create({
    baseURL: 'http://localhost:8000',
    withCredentials: true, // Cookieを毎回送信するために必須
    headers: {
        'Accept': 'application/json',
        'X-Requested-With': 'XMLHttpRequest',
    },
});

// 1. CSRF Cookie初期化 & ログイン関数
async function login(email, password) {
    try {
        // ① CSRF Cookieを取得
        await apiClient.get('/sanctum/csrf-cookie');

        // ② ログインリクエスト
        await apiClient.post('/login', { email, password });

        // ③ ログイン中のユーザー情報を取得
        const userResponse = await apiClient.get('/api/user');
        console.log('ログイン成功:', userResponse.data);
    } catch (error) {
        console.error('ログイン失敗:', error.response?.data || error.message);
    }
}

トークンの有効期限と定期メンテナンス

有効期限の設定(config/sanctum.php)

APIトークン(Personal Access Token)の既定値は無期限です。セキュリティ向上のため有効期限を設定する場合は、config/sanctum.phpexpiration(分単位)を指定します。

// 7日間でトークンを失効させる設定
'expiration' => 60 * 24 * 7,

期限切れトークンの自動削除(sanctum:prune-expired)

期限切れとなったトークンは自動ではデータベースから削除されず、personal_access_tokensテーブルに蓄積されます。定期的にsanctum:prune-expiredコマンドをスケジューラに登録して掃除しましょう。

// routes/console.php にスケジュールを登録
use Illuminate\Support\Facades\Schedule;

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

Sanctum認証のFeatureテスト(自動テスト)

PHPUnitやPestでSanctum認証が必要なエンドポイントをテストする場合、Sanctum::actingAs()ヘルパーを利用すると、実際にトークンを発行する手間を省いて簡単にモック認証が可能です。

<?php

namespace Tests\Feature;

use App\Models\User;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;

class UserApiTest extends TestCase
{
    public function test_authenticated_user_can_access_user_endpoint(): void
    {
        $user = User::factory()->create();

        // 指定ユーザーおよびアビリティで認証状態を偽装
        Sanctum::actingAs($user, ['*']);

        $response = $this->getJson('/api/user');

        $response->assertStatus(200)
                 ->assertJson([
                     'id' => $user->id,
                     'email' => $user->email,
                 ]);
    }

    public function test_unauthenticated_user_cannot_access_user_endpoint(): void
    {
        $response = $this->getJson('/api/user');

        $response->assertStatus(401);
    }
}

よくあるエラー・トラブルシューティング

症状・エラーメッセージ 主な原因 対処法
401 Unauthenticated(トークン送信時) UserモデルにHasApiTokensトレイトが未設定、またはAuthorization: Bearer <token>ヘッダーの記述ミス Userモデルにuse HasApiTokens;を追加し、ヘッダー形式(Bearer とトークンの間に半角スペース)を確認する
419 CSRF token mismatch(SPA認証時) /sanctum/csrf-cookieを事前に呼び出していない、またはSESSION_DOMAINSANCTUM_STATEFUL_DOMAINSの設定ミス ログイン前に必ずGET /sanctum/csrf-cookieを呼び出す。また、SPAとAPIのドメイン・ポート設定を.envで一致させる
CORS error(ブラウザ通信時) config/cors.phpsupports_credentialsfalse、またはオリジン(URL)の許可設定不足 supports_credentials => trueにし、allowed_originsにフロントエンドのURL(例:http://localhost:3000)を追加する
localhostと127.0.0.1の不一致 フロントエンドがlocalhost:3000でリクエスト先が127.0.0.1:8000になっている ブラウザはlocalhost127.0.0.1を別オリジン・別Cookieとして扱うため、どちらか一方に統一する
app/Http/Kernel.php が存在しない Laravel 11以降でKernel.phpが廃止されたため ミドルウェア設定はbootstrap/app.php->withMiddleware()内で行う(SPA認証は$middleware->statefulApi();

まとめ

Laravel Sanctumは、現代のWebアプリケーションにおいて「APIトークン認証」と「CookieベースSPA認証」の両方を過不足なくシンプルに実装できる最も扱いやすい認証パッケージです。

  • 最新Laravelでの導入php artisan install:apiを実行し、UserモデルにHasApiTokensトレイトを追加するだけで準備完了
  • モバイル・外部API:Bearerトークンを発行し、必要に応じてAbilitiesで操作権限を細かく制限
  • 自社SPA(Vue/React等):セキュリティに優れたCookieベースSPA認証(statefulApi())を採用
  • Passportとの使い分け:OAuth2認可サーバー(サードパーティ連携)が必要な場合のみLaravel Passport、それ以外はSanctumで十分

API設計の全体像についてはLaravel API作成完全ガイドもあわせてご覧ください。

よくある質問(FAQ)

Q. Laravel SanctumとLaravel Passportはどちらを選ぶべきですか?

A. 自社SPAやモバイルアプリ向けの認証、あるいはシンプルなトークン認証であればSanctumが最適です。第三者サービスに認可画面(OAuth2プロトコル)を提供してAPIアクセスを許可したい場合のみLaravel Passportを選択してください。

Q. 1つのLaravelアプリでSPA認証とAPIトークン認証を同時に併用できますか?

A. はい、問題なく併用可能です。Sanctumのauth:sanctumガードは、Cookie(セッション)が存在する場合はSPA認証として、Authorization: Bearerヘッダーが存在する場合はトークン認証として自動判別して処理します。

Q. SPA認証でログイン後にセッションが切れる場合は何を確認すべきですか?

A. .envSANCTUM_STATEFUL_DOMAINSにSPAのドメインとポート番号が含まれているか、SESSION_DOMAINが正しく指定されているか、フロントエンド側でwithCredentials: trueが設定されているかをご確認ください。

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント