こんにちは、かつコーチです。
前回はBreeze・Jetstream・Fortify・Sanctumの違いを整理して、「どの認証機能を選べばいいか」の判断軸を紹介しました。
今回はその中でも、SPA連携やモバイルアプリのバックエンドでよく使われるSanctumを使って、実際にAPI認証を実装していきます。
セッション認証とは仕組みがかなり違うので、「なぜトークンが必要なのか」から順を追って説明します。
Sanctumとは?API認証の基本
Sanctumが解決する課題
Sanctumは、SPA(Single Page Application)・モバイルアプリ・単純なAPIトークンによる認証を、軽量に実現するためのLaravel公式パッケージです。
通常のWebアプリでは、ログインするとサーバーがセッション(ログイン状態をサーバー側で保持する仕組み)を発行し、ブラウザはCookieでそのセッションIDをやり取りします。
ですが、フロントエンドとバックエンドが別ドメイン・別プロジェクトになるケースや、モバイルアプリからAPIを叩くケースでは、Cookieベースのセッション認証だけでは対応しづらい場面が出てきます。
- スマホアプリにはブラウザのCookie機構がない
- 別ドメインのSPAからAPIを呼ぶとCookieの扱いが複雑になる
- 単純に「このリクエストは誰からのものか」をトークンで判定したい
こうしたケースに対応するために、SanctumはAPIトークン認証とSPA向けのCookieベース認証の両方を1つのパッケージでサポートしています。
トークン認証とセッション認証の違い
両者の違いを整理すると、次のようになります。
| セッション認証 | トークン認証(Sanctum) | |
|---|---|---|
| 認証情報の保存場所 | サーバー側のセッションストア | データベースのトークンテーブル |
| クライアントが送るもの | セッションCookie | Authorization: Bearer {token} ヘッダー |
| 主な用途 | 通常のWebアプリ(Blade) | モバイルアプリ・外部API連携 |
| ドメインをまたぐ利用 | 苦手 | 得意 |
私が最初にAPI開発を任されたとき、「ログインしてるのにAPIが401(未認証)を返す」という現象で丸一日悩んだことがあります。
原因は単純で、フロントは通常のセッションCookieしか送っていないのに、APIエンドポイント側はAuthorizationヘッダーのトークンを期待していた、という認証方式のミスマッチでした。
「どの認証方式を使っているか」を最初にきちんと確認する癖をつけておくと、こういう事故を防げます。
Sanctumのセットアップと基本実装
インストールとマイグレーション
まずはSanctumをインストールします(Laravel 11以降は初期状態で組み込まれていることが多いですが、手動導入する場合の手順です)。
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
マイグレーションを実行すると、personal_access_tokens というテーブルが作成されます。
ここに、発行したAPIトークンのハッシュ値や有効期限などが保存されます。
Userモデルにトレイトを追加する
トークンを発行できるようにするため、UserモデルにHasApiTokensトレイトを追加します。
<?php
// app/Models/User.php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
// 以下、既存のプロパティ・メソッド
}
HasApiTokensを使うことで、$user->createToken()のようなメソッドが使えるようになります。
ログインしてトークンを発行する
APIログイン用のエンドポイントを作り、認証成功時にトークンを発行します。
<?php
// routes/api.php
use App\Http\Controllers\Api\AuthController;
Route::post('/login', [AuthController::class, 'login']);
<?php
// app/Http/Controllers/Api/AuthController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function login(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['メールアドレスまたはパスワードが正しくありません。'],
]);
}
// "api-token"はこのトークンに付ける名前(管理用のラベル)
$token = $user->createToken('api-token')->plainTextToken;
return response()->json([
'user' => $user,
'token' => $token,
]);
}
}
createToken()が返すplainTextTokenは、発行された瞬間しか平文で取得できません。
データベースにはハッシュ化された値しか保存されないため、レスポンスで返したトークンをクライアント側で必ず保存しておく必要があります。
トークンを使ってAPIにアクセスする
発行したトークンは、リクエストヘッダーに付けて送ります。
curl -X GET https://example.com/api/user \
-H "Authorization: Bearer 1|abcdefghijklmnopqrstuvwxyz"
このヘッダーを受け取るAPI側は、auth:sanctumミドルウェアで認証済みユーザーかどうかをチェックします。
<?php
// routes/api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
auth:sanctumを通過すると、$request->user()でトークンの持ち主のUserインスタンスを取得できます。
つまずきやすいポイント:ミドルウェアの付け忘れ
保護すべきルートに認証ミドルウェアを忘れる
Sanctumを導入していても、ミドルウェアを付け忘れると誰でもアクセスできてしまいます。
これは私が実際にやってしまったミスで、動作確認中に一時的にミドルウェアを外したまま、そのままコミットしてしまったことがありました。
❌ Before:認証ミドルウェアが付いていない
<?php
// routes/api.php
// 誰でもアクセスできてしまう危険な状態
Route::get('/orders', [OrderController::class, 'index']);
Route::post('/orders', [OrderController::class, 'store']);
このままだと、トークンなしでも/ordersにアクセスでき、ログイン不要で注文情報が見えてしまいます。
✅ After:auth:sanctumミドルウェアでグループ化する
<?php
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
Route::get('/orders', [OrderController::class, 'index']);
Route::post('/orders', [OrderController::class, 'store']);
});
複数のルートをまとめて保護したい場合は、group()で囲んでおくと付け忘れを防げます。
個別に->middleware('auth:sanctum')を付けるより、グループ化しておくほうが「保護されたエリア」であることが目視でも分かりやすくなります。
トークンの権限(アビリティ)を絞る
トークンにはアビリティ(そのトークンでできる操作の範囲)を設定できます。
読み取り専用のトークンを発行したい場合は、次のように指定します。
<?php
// 読み取り専用のアビリティだけを持つトークンを発行する
$token = $user->createToken('read-only-token', ['orders:read'])->plainTextToken;
コントローラ側では、そのアビリティを持っているかをチェックできます。
<?php
// app/Http/Controllers/Api/OrderController.php
public function store(Request $request)
{
if (! $request->user()->tokenCan('orders:write')) {
abort(403, '書き込み権限がありません。');
}
// 注文作成処理
}
「このトークンは閲覧専用アプリ用」「こちらは管理画面用」のように用途を分けたい場合、アビリティで細かく権限を制御できます。
応用:SPA向けのCookieベース認証
モバイル・外部API向けとSPA向けの使い分け
Sanctumには、ここまで紹介したトークン認証のほかに、SPA向けのステートフル認証というモードもあります。
同一ドメイン(またはサブドメイン)で動くSPAの場合、トークンを発行せずにCookieベースでセッション認証と同じ仕組みを使えます。
config/sanctum.phpのstatefulにフロントエンドのドメインを登録し、EnsureFrontendRequestsAreStatefulミドルウェアを通すことで有効になります。
<?php
// config/sanctum.php(抜粋)
'stateful' => explode(',', env(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,localhost:3000,127.0.0.1'
)),
判断軸としては、次のように考えるとシンプルです。
- 同一ドメインのSPA(React/Vueなど)→ Cookieベースのステートフル認証
- モバイルアプリ・別ドメインの外部連携 → トークン認証
どちらのモードでも、auth:sanctumミドルウェア自体は共通で使えるのがSanctumの便利なところです。
まとめ
この記事のポイント
- SanctumはAPIトークン認証とSPA向けCookie認証の両方をサポートする軽量パッケージ
HasApiTokensトレイトとcreateToken()でトークンを発行し、Authorization: Bearerヘッダーで送る- 保護したいAPIルートには
auth:sanctumミドルウェアを忘れずグループ化して付ける - アビリティを使うと、トークンごとに操作範囲を絞り込める
- 同一ドメインのSPAならCookieベースのステートフル認証も選択肢になる
次に読むべき記事
API認証ができるようになったら、次は「誰がどのデータを操作できるか」という認可の仕組みを見ていきましょう。
→ 次の記事:Policy・Gateで認可(権限チェック)を実装する