#はじめに
Laravel Sanctum は、SPA(シングルページアプリケーション)、モバイルアプリケーション、シンプルなトークンベースのAPI向けの軽量な認証システムを提供します。Sanctumでは、各ユーザーが複数のAPIトークンを生成でき、それらのトークンに対して許可された操作を指定する権限(abilities / scopes)を付与できます。
#仕組み
Laravel Sanctumは2つの異なる問題を解決するために存在します。まずはそれぞれについて説明し、その後ライブラリの詳細に進みます。
#APIトークン
まず、SanctumはOAuthの複雑さなしにユーザーにAPIトークンを発行できるシンプルなパッケージです。この機能はGitHubなどの「パーソナルアクセストークン」発行機能に触発されています。例えば、アプリケーションの「アカウント設定」画面でユーザーが自分のアカウント用にAPIトークンを生成できるとします。Sanctumを使えば、そのトークンの生成と管理が可能です。これらのトークンは通常、非常に長い有効期限(数年)を持ちますが、ユーザーがいつでも手動で取り消せます。
Laravel Sanctumは、ユーザーのAPIトークンを単一のデータベーステーブルに保存し、Authorization ヘッダーに有効なAPIトークンが含まれているかを検証してHTTPリクエストを認証することで、この機能を提供します。
#SPA認証
次に、SanctumはLaravelをバックエンドに持つAPIと通信する必要があるSPAを簡単に認証する方法を提供します。これらのSPAはLaravelアプリケーションと同じリポジトリに存在する場合もあれば、Vue CLIやNext.jsで作成された全く別のリポジトリの場合もあります。
この機能では、Sanctumはトークンを使用しません。代わりにLaravelの組み込みのクッキーセッション認証を利用します。通常、SanctumはLaravelの web 認証ガードを使ってこれを実現します。これによりCSRF保護やセッション認証が提供され、XSSによる認証情報の漏洩も防げます。
Sanctumは、リクエストが自分のSPAフロントエンドから発信された場合にのみクッキーを使った認証を試みます。リクエストを受け取ると、まず認証クッキーの有無を確認し、なければ Authorization ヘッダーに有効なAPIトークンがあるかをチェックします。
SanctumをAPIトークン認証だけに使うことも、SPA認証だけに使うことも問題ありません。Sanctumを使うからといって両方の機能を使う必要はありません。
#インストール
最新のLaravelにはすでにLaravel Sanctumが含まれています。ただし、アプリケーションの composer.json に laravel/sanctum が含まれていない場合は、以下のインストール手順に従ってください。
Laravel SanctumはComposerパッケージマネージャーでインストールできます:
composer require laravel/sanctum
次に、vendor:publish Artisanコマンドを使ってSanctumの設定ファイルとマイグレーションファイルを公開してください。sanctum の設定ファイルはアプリケーションの config ディレクトリに配置されます:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
最後に、データベースマイグレーションを実行してください。SanctumはAPIトークンを保存するためのテーブルを1つ作成します:
php artisan migrate
次に、SPA認証にSanctumを使う場合は、アプリケーションの app/Http/Kernel.php ファイル内の api ミドルウェアグループにSanctumのミドルウェアを追加してください:
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
#マイグレーションのカスタマイズ
Sanctumのデフォルトマイグレーションを使わない場合は、App\Providers\AppServiceProvider クラスの register メソッド内で Sanctum::ignoreMigrations メソッドを呼び出してください。デフォルトのマイグレーションは次のコマンドでエクスポートできます:php artisan vendor:publish --tag=sanctum-migrations
#設定
#デフォルトモデルのオーバーライド
通常は不要ですが、Sanctumが内部で使用する PersonalAccessToken モデルを拡張することもできます:
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
class PersonalAccessToken extends SanctumPersonalAccessToken
{
// ...
}
その後、Sanctumの usePersonalAccessTokenModel メソッドを使ってカスタムモデルを指定できます。通常はアプリケーションのサービスプロバイダーの boot メソッド内で呼び出します:
use App\Models\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;
/**
* アプリケーションサービスの初期化。
*/
public function boot(): void
{
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}
#APIトークン認証
自分のファーストパーティSPAの認証にAPIトークンを使うべきではありません。代わりにSanctumの組み込みのSPA認証機能を使ってください。
#APIトークンの発行
SanctumではAPIトークン(パーソナルアクセストークン)を発行して、APIリクエストの認証に使えます。APIトークンを使う場合は、Authorization ヘッダーに Bearer トークンとして含めてください。
トークンを発行するには、Userモデルで Laravel\Sanctum\HasApiTokens トレイトを使う必要があります:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
トークンを発行するには createToken メソッドを使います。createToken は Laravel\Sanctum\NewAccessToken インスタンスを返します。APIトークンはデータベースに保存する前にSHA-256でハッシュ化されますが、NewAccessToken の plainTextToken プロパティでプレーンテキストのトークンを取得できます。トークン発行直後にユーザーにこの値を表示してください:
use Illuminate\Http\Request;
Route::post('/tokens/create', function (Request $request) {
$token = $request->user()->createToken($request->token_name);
return ['token' => $token->plainTextToken];
});
ユーザーのすべてのトークンは、HasApiTokens トレイトが提供する tokens Eloquentリレーションシップで取得できます:
foreach ($user->tokens as $token) {
// ...
}
#トークンの権限
Sanctumではトークンに「権限(abilities)」を割り当てられます。これはOAuthの「スコープ」と似た役割です。createToken メソッドの第2引数に文字列の権限配列を渡せます:
return $user->createToken('token-name', ['server:update'])->plainTextToken;
Sanctumで認証されたリクエストを処理する際、tokenCan メソッドでトークンに特定の権限があるか判定できます:
if ($user->tokenCan('server:update')) {
// ...
}
#トークン権限ミドルウェア
Sanctumには、リクエストが特定の権限を持つトークンで認証されているか検証する2つのミドルウェアが含まれています。まず、アプリケーションの app/Http/Kernel.php ファイルの $middlewareAliases プロパティに以下を追加してください:
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
abilities ミドルウェアは、リクエストのトークンが指定したすべての権限を持つか検証します:
Route::get('/orders', function () {
// トークンが "check-status" と "place-orders" の両方の権限を持つ場合...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);
ability ミドルウェアは、リクエストのトークンが指定した権限のうち いずれか一つ を持つか検証します:
Route::get('/orders', function () {
// トークンが "check-status" または "place-orders" の権限を持つ場合...
})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);
#ファーストパーティUI発行リクエスト
利便性のため、Sanctumの tokenCan メソッドは、認証済みリクエストがファーストパーティSPAからのものでSanctumの組み込みSPA認証を使っている場合、常に true を返します。
ただし、これは必ずしもアプリケーションがユーザーにその操作を許可することを意味しません。通常はアプリケーションの認可ポリシーが、トークンに権限が付与されているかとユーザー自身がその操作を許可されているかを判定します。
例えば、サーバー管理アプリケーションを想定すると、トークンがサーバーの更新権限を持ち、かつそのサーバーがユーザーに属しているかを確認することになります:
return $request->user()->id === $server->user_id &&
$request->user()->tokenCan('server:update')
最初は、ファーストパーティUI発行リクエストに対して tokenCan メソッドが常に true を返すのは不自然に思えるかもしれません。しかし、APIトークンが常に利用可能で tokenCan で検査できると仮定できるのは便利です。この方法なら、アプリケーションの認可ポリシー内でリクエストがUIからのものかAPIのサードパーティからのものかを気にせずに tokenCan を呼べます。
#ルートの保護
すべてのリクエストを認証済みにするには、routes/web.php と routes/api.php の保護したいルートに sanctum 認証ガードを適用してください。このガードは、リクエストがステートフルなクッキー認証済みか、サードパーティからのリクエストなら有効なAPIトークンを含むかを検証します。
なぜ routes/web.php のルートも sanctum ガードで認証することを推奨するのか疑問に思うかもしれません。SanctumはまずLaravelの通常のセッション認証クッキーで認証を試み、クッキーがなければ Authorization ヘッダーのトークンで認証を試みます。さらに、すべてのリクエストをSanctumで認証することで、現在認証中のユーザーインスタンスに対して常に tokenCan メソッドを呼べるようになります:
use Illuminate\Http\Request;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
#トークンの取り消し
Laravel\Sanctum\HasApiTokens トレイトが提供する tokens リレーションシップを使って、データベースからトークンを削除し「取り消し」できます:
// すべてのトークンを取り消す...
$user->tokens()->delete();
// 現在のリクエストで使われているトークンを取り消す...
$request->user()->currentAccessToken()->delete();
// 特定のトークンを取り消す...
$user->tokens()->where('id', $tokenId)->delete();
#トークンの有効期限
デフォルトでは、Sanctum のトークンは期限切れにならず、トークンの取り消しによってのみ無効化されます。ただし、アプリケーションの API トークンに有効期限を設定したい場合は、アプリケーションの sanctum 設定ファイル内の expiration 設定オプションで指定できます。この設定オプションは、発行されたトークンが期限切れと見なされるまでの分数を定義します。
'expiration' => 525600,
各トークンの有効期限を個別に指定したい場合は、createToken メソッドの第3引数として有効期限を渡すことで設定できます。
return $user->createToken(
'token-name', ['*'], now()->addWeek()
)->plainTextToken;
アプリケーションにトークンの有効期限を設定している場合は、スケジュールタスクを使って期限切れのトークンを削除することも検討してください。幸いなことに、Sanctum には sanctum:prune-expired という Artisan コマンドが用意されており、これを使って期限切れトークンを削除できます。例えば、少なくとも24時間期限切れのトークンをすべて削除するスケジュールタスクを設定できます。
$schedule->command('sanctum:prune-expired --hours=24')->daily();
#SPA 認証
Sanctum は、Laravel をバックエンドに持つ API と通信するシングルページアプリケーション(SPA)を簡単に認証する方法も提供します。これらの SPA は Laravel アプリケーションと同じリポジトリに存在する場合もあれば、まったく別のリポジトリで管理されている場合もあります。
この機能では、Sanctum はトークンを使用しません。代わりに、Laravel の組み込みのクッキーを使ったセッション認証サービスを利用します。この認証方法は、CSRF 保護やセッション認証の利点を提供し、XSS による認証情報の漏洩も防ぎます。
認証を行うには、SPA と API が同じトップレベルドメインを共有している必要があります。ただし、異なるサブドメインに配置しても問題ありません。さらに、リクエストには Accept: application/json ヘッダーと、Referer または Origin ヘッダーのいずれかを必ず送信してください。
#設定
#ファーストパーティドメインの設定
まず、SPA がリクエストを送信するドメインを設定します。これらのドメインは、sanctum 設定ファイルの stateful オプションで指定できます。この設定は、API へのリクエスト時に Laravel のセッションクッキーを使った「ステートフル」認証を維持するドメインを決定します。
ポート番号を含む URL(例:127.0.0.1:8000)でアプリケーションにアクセスする場合は、ドメインにポート番号も含めるようにしてください。
#Sanctum ミドルウェア
次に、app/Http/Kernel.php ファイルの api ミドルウェアグループに Sanctum のミドルウェアを追加します。このミドルウェアは、SPA からのリクエストが Laravel のセッションクッキーを使って認証できるようにしつつ、サードパーティやモバイルアプリからのリクエストは API トークンで認証できるようにします。
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
#CORS とクッキー
別のサブドメインで実行される SPA からアプリケーションに認証できない場合は、CORS(クロスオリジンリソースシェアリング)やセッションクッキーの設定が誤っている可能性があります。
アプリケーションの CORS 設定で、Access-Control-Allow-Credentials ヘッダーが True の値で返されていることを確認してください。これは、config/cors.php 設定ファイルの supports_credentials オプションを true に設定することで実現できます。
さらに、アプリケーションのグローバルな axios インスタンスで withCredentials と withXSRFToken オプションを有効にしてください。通常は resources/js/bootstrap.js ファイルで設定します。Axios を使わずに HTTP リクエストを送る場合は、同等の設定を自分の HTTP クライアントで行ってください。
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
最後に、アプリケーションのセッションクッキーのドメイン設定がルートドメインの任意のサブドメインをサポートしていることを確認してください。これは、config/session.php 設定ファイルでドメインの先頭に . を付けることで実現できます。
'domain' => '.domain.com',
#認証
#CSRF 保護
SPA を認証するには、まず SPA の「ログイン」ページで /sanctum/csrf-cookie エンドポイントにリクエストを送り、アプリケーションの CSRF 保護を初期化します。
axios.get('/sanctum/csrf-cookie').then(response => {
// ログイン...
});
このリクエスト中に、Laravel は現在の CSRF トークンを含む XSRF-TOKEN クッキーを設定します。このトークンは、その後のリクエストで X-XSRF-TOKEN ヘッダーに渡す必要があります。Axios や Angular HttpClient のような一部の HTTP クライアントライブラリはこれを自動で行います。もし JavaScript の HTTP ライブラリが自動で設定しない場合は、このルートで設定された XSRF-TOKEN クッキーの値と一致するように X-XSRF-TOKEN ヘッダーを手動で設定してください。
#ログイン
CSRF 保護が初期化されたら、Laravel アプリケーションの /login ルートに POST リクエストを送ります。この /login ルートは、手動で実装するか、Laravel Fortify のようなヘッドレス認証パッケージを使って実装できます。
ログインリクエストが成功すると認証され、その後のアプリケーションのルートへのリクエストは、Laravel アプリケーションがクライアントに発行したセッションクッキーを使って自動的に認証されます。さらに、アプリケーションはすでに /sanctum/csrf-cookie ルートにリクエストを送っているため、JavaScript の HTTP クライアントが XSRF-TOKEN クッキーの値を X-XSRF-TOKEN ヘッダーに送信していれば、その後のリクエストは自動的に CSRF 保護を受けます。
もちろん、ユーザーのセッションが非アクティブによって期限切れになると、その後の Laravel アプリケーションへのリクエストは 401 または 419 の HTTP エラーを受け取る可能性があります。この場合は、ユーザーを SPA のログインページにリダイレクトしてください。
独自の /login エンドポイントを作成しても構いませんが、必ず Laravel が提供する標準のセッションベース認証サービスを使ってユーザーを認証するようにしてください。通常は web 認証ガードを使います。
#ルートの保護
すべてのリクエストに認証を要求するには、routes/api.php ファイルの API ルートに sanctum 認証ガードを適用します。このガードは、SPA からのステートフル認証リクエストか、サードパーティからの有効な API トークンを含むリクエストかを判別して認証します。
use Illuminate\Http\Request;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
#プライベートブロードキャストチャンネルの認可
SPA がプライベート / プレゼンスブロードキャストチャンネルを認証する必要がある場合は、routes/api.php ファイルに Broadcast::routes メソッドを配置してください。
Broadcast::routes(['middleware' => ['auth:sanctum']]);
次に、Pusher の認可リクエストを成功させるために、Laravel Echo を初期化する際にカスタムの Pusher authorizer を提供する必要があります。これにより、アプリケーションは クロスドメインリクエストに対して適切に設定された axios インスタンスを使用するように Pusher を構成できます:
window.Echo = new Echo({
broadcaster: "pusher",
cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
encrypted: true,
key: import.meta.env.VITE_PUSHER_APP_KEY,
authorizer: (channel, options) => {
return {
authorize: (socketId, callback) => {
axios.post('/api/broadcasting/auth', {
socket_id: socketId,
channel_name: channel.name
})
.then(response => {
callback(false, response.data);
})
.catch(error => {
callback(true, error);
});
}
};
},
})
#モバイルアプリケーション認証
Sanctum トークンは、モバイルアプリケーションの API リクエスト認証にも使えます。モバイルアプリの認証プロセスはサードパーティの API リクエスト認証に似ていますが、API トークンの発行方法に若干の違いがあります。
#API トークンの発行
まず、ユーザーのメールアドレス/ユーザー名、パスワード、デバイス名を受け取り、それらの認証情報を新しい Sanctum トークンと交換するルートを作成します。この「デバイス名」は情報用で、任意の値を指定できます。一般的には、ユーザーが認識しやすい名前(例:「Nuno の iPhone 12」)を使います。
通常は、モバイルアプリの「ログイン」画面からこのトークン発行エンドポイントにリクエストを送ります。エンドポイントはプレーンテキストの API トークンを返し、それをモバイル端末に保存して追加の API リクエストに使います。
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',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['提供された認証情報が正しくありません。'],
]);
}
return $user->createToken($request->device_name)->plainTextToken;
});
モバイルアプリがこのトークンを使って API リクエストを送る際は、Authorization ヘッダーに Bearer トークンとして渡してください。
モバイルアプリ用にトークンを発行する際は、トークンの権限を指定しても構いません。
#ルートの保護
前述の通り、すべてのリクエストに認証を要求するには、sanctum 認証ガードをルートに適用します。
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
#トークンの取り消し
ユーザーがモバイルデバイスに発行された API トークンを取り消せるように、Web アプリケーションの「アカウント設定」画面などでトークン名と「取り消し」ボタンを一覧表示できます。ユーザーが「取り消し」ボタンを押すと、データベースからトークンを削除できます。ユーザーの API トークンには、Laravel\Sanctum\HasApiTokens トレイトが提供する tokens リレーションからアクセスできます。
// すべてのトークンを取り消す...
$user->tokens()->delete();
// 特定のトークンを取り消す...
$user->tokens()->where('id', $tokenId)->delete();
#テスト
テスト時には、Sanctum::actingAs メソッドを使ってユーザーを認証し、そのトークンに付与する権限を指定できます。
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/task');
$response->assertOk();
}
すべての権限をトークンに付与したい場合は、actingAs メソッドの権限リストに * を含めてください。
Sanctum::actingAs(
User::factory()->create(),
['*']
);