LaravelでAPI認証を実装する方法(Sanctum)

LaravelでAPI認証を実装する方法(Sanctum)

Laravel Sanctumは、LaravelでAPI認証を実装するときに最も扱いやすい選択肢の1つ。特に、自社のSPA、モバイルアプリ、社内ツール、シンプルな外部連携APIのように、OAuth2の大掛かりな仕組みまでは不要だが、確実にAPIを保護したい場面で強い。実務では「ログイン後にトークンを発行してAPIを叩く方式」と、「SPAがCookieベースで認証される方式」の2系統を整理しておくと設計がぶれにくい。この記事では、Sanctumを使ったAPI認証の導入、トークン発行、保護ルート、権限制御、失効処理、よくあるエラーまでを一通り整理する。

Sanctumが向いているケース

Sanctumが向いているのは次のような構成。
・自社のフロントエンドとLaravel APIを接続したい
・モバイルアプリ向けにトークン認証を用意したい
・ユーザー単位で複数トークンを管理したい
・OAuth2の認可コードフローまでは不要
・まずは軽量で実装しやすいAPI認証を採用したい
逆に、第三者アプリ向けの本格的なOAuth2認可サーバーが必要なら、SanctumよりPassportの方が適している。

Sanctumの認証方式は2種類ある

Sanctumには大きく2つの使い方がある。
1つ目は、Bearerトークンを発行して Authorization: Bearer ... で認証する方式。
2つ目は、SPAがLaravelのセッションCookieを使って認証される方式。
この2つは同じSanctumでも動き方がかなり違うため、最初に「トークン方式でいくのか」「SPA Cookie方式でいくのか」を決めておく必要がある。
モバイルアプリやシンプルな外部クライアントならトークン方式、同一組織のSPAならCookie方式が扱いやすい。

導入の基本手順

LaravelのAPIルートとSanctumの基盤を整えるには、まずAPIインストールを実行する。
これにより、API認証に必要な土台が入り、routes/api.php も用意される。

php artisan install:api

プロジェクトによっては、すでにAPIルートが存在することもあるが、その場合でもSanctumを使う前提でルートと認証方式を整理した方が安全。
発生しやすい問題は、
・APIルートが無い
・Sanctum前提で進めているのに install が未実行
・既存構成と新規構成が混ざって認証ガードが分からなくなる
といったもの。

Userモデルの準備

Sanctumでトークンを発行するには、認証対象のモデルに HasApiTokens トレイトを追加する。通常は User モデルに付ける。

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens;

// ...

}

これを入れておくことで、ユーザーから createToken() を呼べるようになる。
発生しやすいエラー条件は、
HasApiTokens を付け忘れて createToken() が呼べない
・認証対象が User 以外なのに、そのモデル側設定をしていない
というもの。

ログイン処理でトークンを発行する

Sanctumのトークン認証では、ログイン成功後にトークンを発行して返すのが基本になる。
以下はシンプルな例。

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;

public function login(Request $request)
{
$request->validate([
‘email’ => [‘required’, ‘email’],
‘password’ => [‘required’, ‘string’],
]);

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

if (! $user || ! Hash::check($request->password, $user->password)) {
    return response()->json([
        'message' => '認証情報が正しくありません。'
    ], 422);
}

$token = $user->createToken('api-token')->plainTextToken;

return response()->json([
    'token' => $token,
    'user' => $user,
]);

}

この実装でよくある問題は、
・ログインは成功しているのにトークンを返していない
・トークンは返しているが、クライアントが保存していない
・パスワード照合ではなく平文比較をしている
など。

保護したいルートにauth:sanctumを付ける

トークンを発行しただけでは意味がなく、認証が必要なルートを auth:sanctum で保護する必要がある。

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware(‘auth:sanctum’)->get(‘/user’, function (Request $request) {
return $request->user();
});

複数ルートをまとめて保護するならグループ化すると分かりやすい。

Route::middleware(‘auth:sanctum’)->group(function () {
Route::get(‘/user’, function (Request $request) {
return $request->user();
});

Route::get('/orders', [OrderController::class, 'index']);
Route::post('/orders', [OrderController::class, 'store']);

});

発生しやすい問題は、
・ミドルウェアを付け忘れて認証無しでアクセスできてしまう
・全部のルートに付けてしまい公開APIまで401になる
auth:apiauth:sanctum を混同する
というもの。

クライアントからBearerトークンでアクセスする

発行したトークンは、API呼び出し時に Authorization ヘッダで送る。

curl -X GET http://localhost/api/user \
-H “Accept: application/json” \
-H “Authorization: Bearer YOUR_TOKEN”

JavaScriptで送るなら次のような形になる。

fetch(‘/api/user’, {
method: ‘GET’,
headers: {
‘Accept’: ‘application/json’,
‘Authorization’: ‘Bearer YOUR_TOKEN’
}
});

ここでのエラー発生条件はかなり多い。
Bearer を付けずにトークン文字列だけ送っている
Authorization ヘッダ自体を送っていない
・古いトークンや失効済みトークンを使っている
・フロントでトークン保存先が不安定
という問題が起きやすい。

トークンごとに権限を分ける(abilities)

Sanctumでは、トークンごとに abilities を持たせることができる。
これは「読み取り専用トークン」「管理操作可能トークン」のように、トークン単位で権限を分けたいときに便利。

$token = $user->createToken(‘report-token’, [‘reports:read’])->plainTextToken;

チェックは次のように行う。

if (! $request->user()->tokenCan(‘reports:read’)) {
abort(403, ‘このトークンでは参照できません。’);
}

発生しやすい問題は、
・abilities を付けていないのに tokenCan() で制御しようとする
・何でもフル権限で発行してしまい、権限設計が意味を失う
というケース。
実務では、トークンに役割を持たせる設計にすると管理しやすい。

トークンの一覧管理と削除

ユーザーが複数端末から利用する場合、トークンを一覧管理できると便利。
Sanctumでは、ユーザーが持っているトークンを取得して削除できる。

// 全トークン取得
$tokens = $request->user()->tokens;

// 現在使っているトークンだけ削除
$request->user()->currentAccessToken()->delete();

// 全トークン削除
$request->user()->tokens()->delete();

ログアウト時に現在トークンだけ削除するのか、全端末分を削除するのかは、要件によって決める必要がある。
発生しやすい問題は、
・ログアウトしてもトークンが残り続ける
・不要な古いトークンが大量に残る
・どの端末用トークンか分からなくなる
という運用面の問題。

SPAで使う場合の考え方

SanctumはSPAのCookie認証にも対応している。ただし、これはBearerトークン方式とは別物。
SPA認証で必要になるのは、
・statefulドメイン設定
・CSRF Cookieの取得
・セッションクッキー送信
・CORS/withCredentials の整合
など。
そのため、SPA認証をやる場合は「トークン認証の記事」と完全に同じ流れでは進まない。
フロントが別ドメインにあるのか、同一ドメインなのかで設定も変わるため、最初に構成を固定する必要がある。

Sanctumだけでは認証UIは作られない

SanctumはAPIトークンと認証状態を扱う仕組みであって、ログイン画面、会員登録、パスワードリセット画面などを自動生成するものではない。
つまり、次のような機能は別途作る必要がある。
・ログインフォーム
・登録API
・パスワード再設定
・メール認証
必要なら Fortify や Starter Kit を併用する構成も現実的になる。

よくあるエラーと発生条件

Sanctum導入時によく起きる問題をまとめると次の通り。
・401 Unauthorized
発生条件:Bearer未送信、トークン不正、失効済み、auth:sanctum 未設定
・419 Page Expired
発生条件:SPAでCSRF Cookie取得不足、Cookie認証周りの不整合
Call to undefined method createToken()
発生条件:HasApiTokens 未追加
・本番だけ認証できない
発生条件:Cookieドメイン、HTTPS、SameSite、stateful設定不整合
・abilities が効かない
発生条件:付与していない、使い方が曖昧
これらはほとんどがSanctum本体の問題ではなく、「認証方式の選択ミス」か「設定不整合」で起きる。

まとめ

LaravelでAPI認証を実装する際、Sanctumは非常に現実的で扱いやすい選択肢になる。
特に、
・シンプルなBearerトークン認証
・SPA向けCookie認証
・トークンごとの権限制御
を1つの仕組みでまとめやすい点が強い。
導入時は、
・トークン方式かSPA方式かを先に決める
HasApiTokens を入れる
・ログイン時に createToken() で発行する
auth:sanctum で保護する
・不要トークンを削除・管理する
という流れで組むと整理しやすい。
OAuth2ほど大掛かりな認可基盤が不要であれば、SanctumはLaravelで最初に検討すべきAPI認証手段になりやすい。