Laravel Sanctum
イントロダクション
Laravel Sanctumは、SPA(シングルページアプリケーション)、モバイルアプリケーション、およびシンプルなトークンベースのAPIに、軽量な認証システムを提供します。Sanctumを使用すると、アプリケーションの各ユーザーが自分のアカウント用に複数のAPIトークンを生成できます。これらのトークンには、トークンが実行を許可されているアクションを指定するアビリティ/スコープを付与できます。
仕組み
Laravel Sanctumは、2つの別々の問題を解決するために存在します。ライブラリを深く掘り下げる前に、それぞれについて説明しましょう。
APIトークン
まず、Sanctumは、OAuthの複雑さなしにユーザーへAPIトークンを発行するために使用できるシンプルなパッケージです。この機能は、GitHubや「パーソナルアクセストークン」を発行する他のアプリケーションから着想を得ています。たとえば、アプリケーションの「アカウント設定」に、ユーザーがアカウントのAPIトークンを生成できる画面があると想像してください。Sanctumを使用して、これらのトークンを生成および管理できます。これらのトークンは通常、非常に長い有効期限(数年)を持ちますが、ユーザーはいつでも手動で無効にできます。
Laravel Sanctumは、ユーザーAPIトークンを単一のデータベーステーブルに保存し、有効なAPIトークンを含む必要があるAuthorizationヘッダを介して受信HTTPリクエストを認証することで、この機能を提供します。
SPA認証
次に、Sanctumは、Laravelを利用したAPIと通信する必要があるシングルページアプリケーション(SPA)を認証する簡単な方法を提供するために存在します。これらのSPAは、Laravelアプリケーションと同じリポジトリに存在する場合もあれば、Next.jsやNuxtを使用して作成されたSPAなど、完全に別のリポジトリである場合もあります。
この機能のために、Sanctumは一切トークンを使用しません。代わりに、SanctumはLaravelに組み込まれているクッキーベースのセッション認証サービスを使用します。通常、SanctumはLaravelのweb認証ガードを利用してこれを実現します。これにより、CSRF保護、セッション認証の利点が得られるだけでなく、XSSによる認証情報の漏洩も防ぎます。
Sanctumは、受信リクエストがあなた自身のSPAフロントエンドから発信された場合にのみ、クッキーを使用した認証を試みます。Sanctumが受信HTTPリクエストを調べるとき、最初に認証クッキーをチェックし、存在しない場合、SanctumはAuthorizationヘッダで有効なAPIトークンを調べます。
SanctumをAPIトークン認証のみ、またはSPA認証のみに使用することは全く問題ありません。Sanctumを使用するからといって、それが提供する両方の機能を使用する必要はありません。
インストール
Laravel Sanctumは、install:api Artisanコマンドでインストールできます。
1php artisan install:api
次に、Sanctumを利用してSPAを認証する予定の場合は、このドキュメントのSPA認証セクションを参照してください。
設定
デフォルトモデルのオーバーライド
通常は必須ではありませんが、Sanctumが内部で使用するPersonalAccessTokenモデルを自由に拡張できます。
1use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;2 3class PersonalAccessToken extends SanctumPersonalAccessToken4{5 // ...6}
次に、Sanctumが提供するusePersonalAccessTokenModelメソッドを介して、カスタムモデルを使用するようにSanctumへ指示できます。通常、このメソッドはアプリケーションのAppServiceProviderファイルのbootメソッドで呼び出す必要があります。
1use App\Models\Sanctum\PersonalAccessToken; 2use Laravel\Sanctum\Sanctum; 3 4/** 5 * Bootstrap any application services. 6 */ 7public function boot(): void 8{ 9 Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);10}
APIトークン認証
自社製のファーストパーティSPAを認証するためにAPIトークンを使用してはいけません。代わりに、Sanctumに組み込まれているSPA認証機能を使用してください。
APIトークンの発行
Sanctumでは、アプリケーションへのAPIリクエストを認証するために使用できるAPIトークン/パーソナルアクセストークンを発行できます。APIトークンを使用してリクエストを行う場合、トークンはAuthorizationヘッダにBearerトークンとして含める必要があります。
ユーザーへのトークンの発行を開始するには、UserモデルでLaravel\Sanctum\HasApiTokensトレイトを使用する必要があります。
1use Laravel\Sanctum\HasApiTokens;2 3class User extends Authenticatable4{5 use HasApiTokens, HasFactory, Notifiable;6}
トークンを発行するには、createTokenメソッドを使用します。createTokenメソッドはLaravel\Sanctum\NewAccessTokenインスタンスを返します。APIトークンはデータベースに保存される前にSHA-256ハッシュを使用してハッシュ化されますが、NewAccessTokenインスタンスのplainTextTokenプロパティを使用してトークンのプレーンテキスト値にアクセスできます。トークンが作成された直後にこの値をユーザーに表示する必要があります。
1use Illuminate\Http\Request;2 3Route::post('/tokens/create', function (Request $request) {4 $token = $request->user()->createToken($request->token_name);5 6 return ['token' => $token->plainTextToken];7});
HasApiTokensトレイトが提供するtokens Eloquentリレーションを使用して、ユーザーのすべてのトークンにアクセスできます。
1foreach ($user->tokens as $token) {2 // ...3}
トークンアビリティ
Sanctumでは、トークンに「アビリティ」を割り当てることができます。アビリティは、OAuthの「スコープ」と同様の目的を果たします。文字列アビリティの配列をcreateTokenメソッドの第2引数として渡すことができます。
1return $user->createToken('token-name', ['server:update'])->plainTextToken;
Sanctumによって認証された受信リクエストを処理する際に、tokenCanまたはtokenCantメソッドを使用して、トークンが特定のアビリティを持っているかどうかを判断できます。
1if ($user->tokenCan('server:update')) {2 // ...3}4 5if ($user->tokenCant('server:update')) {6 // ...7}
トークンアビリティミドルウェア
Sanctumには、受信リクエストが特定のアビリティを付与されたトークンで認証されていることを確認するために使用できる2つのミドルウェアも含まれています。まず、アプリケーションのbootstrap/app.phpファイルで以下のミドルウェアエイリアスを定義します。
1use Laravel\Sanctum\Http\Middleware\CheckAbilities;2use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;3 4->withMiddleware(function (Middleware $middleware) {5 $middleware->alias([6 'abilities' => CheckAbilities::class,7 'ability' => CheckForAnyAbility::class,8 ]);9})
abilitiesミドルウェアは、受信リクエストのトークンがリストされているすべてのアビリティを持っていることを確認するためにルートに割り当てることができます。
1Route::get('/orders', function () {2 // Token has both "check-status" and "place-orders" abilities...3})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);
abilityミドルウェアは、受信リクエストのトークンがリストされているアビリティの*少なくとも1つ*を持っていることを確認するためにルートに割り当てることができます。
1Route::get('/orders', function () {2 // Token has the "check-status" or "place-orders" ability...3})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);
ファーストパーティUIから開始されたリクエスト
利便性のため、受信した認証済みリクエストがファーストパーティのSPAからのもので、Sanctumの組み込みSPA認証を使用している場合、tokenCanメソッドは常にtrueを返します。
ただし、これは必ずしもアプリケーションがユーザーにアクションの実行を許可しなければならないことを意味するものではありません。通常、アプリケーションの認可ポリシーが、トークンにアビリティを実行する権限が付与されているかどうか、およびユーザーインスタンス自体がアクションを実行できるかどうかを判断します。
たとえば、サーバーを管理するアプリケーションを想像すると、これはトークンがサーバーを更新する権限を持っていること、**かつ**そのサーバーがユーザーに属していることを確認することを意味する場合があります。
1return $request->user()->id === $server->user_id &&2 $request->user()->tokenCan('server:update')
最初は、tokenCanメソッドを呼び出してファーストパーティUIから開始されたリクエストに対して常にtrueを返すことを許可するのは奇妙に思えるかもしれません。しかし、APIトークンが常に利用可能であり、tokenCanメソッドを介して検査できると常に仮定できるのは便利です。このアプローチを取ることで、リクエストがアプリケーションのUIからトリガーされたか、APIのサードパーティコンシューマによって開始されたかを心配することなく、アプリケーションの認可ポリシー内で常にtokenCanメソッドを呼び出すことができます。
ルートの保護
すべての受信リクエストが認証される必要があるようにルートを保護するには、routes/web.phpおよびroutes/api.phpルートファイル内の保護されたルートにsanctum認証ガードをアタッチする必要があります。このガードは、受信リクエストがステートフルなクッキー認証済みリクエストであるか、リクエストがサードパーティからの場合は有効なAPIトークンヘッダを含んでいることを保証します。
なぜアプリケーションのroutes/web.phpファイル内のルートをsanctumガードを使用して認証することを推奨するのか疑問に思うかもしれません。覚えておいてください、SanctumはまずLaravelの典型的なセッション認証クッキーを使用して受信リクエストを認証しようとします。そのクッキーが存在しない場合、SanctumはリクエストのAuthorizationヘッダ内のトークンを使用してリクエストを認証しようとします。さらに、すべてのリクエストをSanctumを使用して認証することで、現在認証されているユーザーインスタンスで常にtokenCanメソッドを呼び出せるようになります。
1use Illuminate\Http\Request;2 3Route::get('/user', function (Request $request) {4 return $request->user();5})->middleware('auth:sanctum');
トークンの無効化
Laravel\Sanctum\HasApiTokensトレイトによって提供されるtokensリレーションシップを使用してデータベースからトークンを削除することで、トークンを「無効化」できます。
1// Revoke all tokens...2$user->tokens()->delete();3 4// Revoke the token that was used to authenticate the current request...5$request->user()->currentAccessToken()->delete();6 7// Revoke a specific token...8$user->tokens()->where('id', $tokenId)->delete();
トークンの有効期限
デフォルトでは、Sanctumトークンは有効期限が切れず、トークンを無効化することによってのみ無効にできます。ただし、アプリケーションのAPIトークンに有効期限を設定したい場合は、アプリケーションのsanctum設定ファイルで定義されているexpiration設定オプションを介して設定できます。この設定オプションは、発行されたトークンが期限切れと見なされるまでの分数を定義します。
1'expiration' => 525600,
各トークンの有効期限を個別に指定したい場合は、createTokenメソッドの第3引数として有効期限を指定することで可能です。
1return $user->createToken(2 'token-name', ['*'], now()->addWeek()3)->plainTextToken;
アプリケーションにトークンの有効期限を設定した場合、期限切れのトークンを整理するためのタスクをスケジュールすることもできます。幸いなことに、Sanctumにはこれを実現するために使用できるsanctum:prune-expired Artisanコマンドが含まれています。たとえば、少なくとも24時間以上期限切れになっているすべての期限切れトークンデータベースレコードを削除するスケジュールタスクを設定できます。
1use Illuminate\Support\Facades\Schedule;2 3Schedule::command('sanctum:prune-expired --hours=24')->daily();
SPA認証
Sanctumは、Laravelを利用したAPIと通信する必要があるシングルページアプリケーション(SPA)を認証する簡単な方法を提供するためにも存在します。これらのSPAは、Laravelアプリケーションと同じリポジトリに存在することも、完全に別のリポジトリであることもあります。
この機能のために、Sanctumは一切トークンを使用しません。代わりに、SanctumはLaravelに組み込まれているクッキーベースのセッション認証サービスを使用します。この認証アプローチは、CSRF保護、セッション認証の利点を提供し、XSSによる認証情報の漏洩も防ぎます。
認証するためには、SPAとAPIが同じトップレベルドメインを共有する必要があります。ただし、異なるサブドメインに配置することは可能です。さらに、リクエストと共にAccept: application/jsonヘッダと、RefererまたはOriginヘッダのいずれかを送信することを確認してください。
設定
ファーストパーティドメインの設定
まず、SPAがリクエストを行うドメインを設定する必要があります。これらのドメインは、sanctum設定ファイルのstateful設定オプションを使用して設定できます。この設定は、APIへリクエストを行う際に、どのドメインがLaravelセッションクッキーを使用して「ステートフル」な認証を維持するかを決定します。
ポート番号を含むURL(127.0.0.1:8000)経由でアプリケーションにアクセスしている場合は、ドメインにポート番号を含めるようにしてください。
Sanctumミドルウェア
次に、SPAからの受信リクエストがLaravelのセッションクッキーを使用して認証できることをLaravelに指示しつつ、サードパーティやモバイルアプリケーションからのリクエストはAPIトークンを使用して認証できるようにする必要があります。これは、アプリケーションのbootstrap/app.phpファイルでstatefulApiミドルウェアメソッドを呼び出すことで簡単に実現できます。
1->withMiddleware(function (Middleware $middleware) {2 $middleware->statefulApi();3})
CORSとクッキー
別のサブドメインで実行されているSPAからアプリケーションで認証する際に問題が発生している場合、CORS(Cross-Origin Resource Sharing)またはセッションクッキーの設定が間違っている可能性があります。
config/cors.php設定ファイルはデフォルトでは公開されません。LaravelのCORSオプションをカスタマイズする必要がある場合は、config:publish Artisanコマンドを使用して完全なcors設定ファイルを公開する必要があります。
1php artisan config:publish cors
次に、アプリケーションのCORS設定がAccess-Control-Allow-CredentialsヘッダをTrueの値で返していることを確認する必要があります。これは、アプリケーションのconfig/cors.php設定ファイル内のsupports_credentialsオプションをtrueに設定することで実現できます。
さらに、アプリケーションのグローバルなaxiosインスタンスでwithCredentialsおよびwithXSRFTokenオプションを有効にする必要があります。通常、これはresources/js/bootstrap.jsファイルで実行する必要があります。フロントエンドからHTTPリクエストを行うためにAxiosを使用していない場合は、独自のHTTPクライアントで同等の設定を行う必要があります。
1axios.defaults.withCredentials = true;2axios.defaults.withXSRFToken = true;
最後に、アプリケーションのセッションクッキードメイン設定がルートドメインの任意のサブドメインをサポートしていることを確認する必要があります。これは、アプリケーションのconfig/session.php設定ファイル内のドメインの先頭に.を付けることで実現できます。
1'domain' => '.domain.com',
認証
CSRF保護
SPAを認証するには、SPAの「ログイン」ページがまず/sanctum/csrf-cookieエンドポイントにリクエストを送信して、アプリケーションのCSRF保護を初期化する必要があります。
1axios.get('/sanctum/csrf-cookie').then(response => {2 // Login...3});
このリクエスト中に、Laravelは現在のCSRFトークンを含むXSRF-TOKENクッキーを設定します。このトークンはURLデコードされ、後続のリクエストでX-XSRF-TOKENヘッダとして渡されるべきです。AxiosやAngular HttpClientなどの一部のHTTPクライアントライブラリはこれを自動的に行います。JavaScriptのHTTPライブラリが値を設定しない場合は、このルートによって設定されたXSRF-TOKENクッキーのURLデコードされた値と一致するように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トークンヘッダを含んでいることを保証します。
1use Illuminate\Http\Request;2 3Route::get('/user', function (Request $request) {4 return $request->user();5})->middleware('auth:sanctum');
プライベートブロードキャストチャンネルの認可
SPAがプライベート/プレゼンスブロードキャストチャンネルで認証する必要がある場合、アプリケーションのbootstrap/app.phpファイルに含まれるwithRoutingメソッドからchannelsエントリを削除する必要があります。代わりに、アプリケーションのブロードキャストルートに正しいミドルウェアを指定できるように、withBroadcastingメソッドを呼び出す必要があります。
1return Application::configure(basePath: dirname(__DIR__))2 ->withRouting(3 web: __DIR__.'/../routes/web.php',4 // ...5 )6 ->withBroadcasting(7 __DIR__.'/../routes/channels.php',8 ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']],9 )
次に、Pusherの認可リクエストが成功するためには、Laravel Echoを初期化する際にカスタムのPusher authorizerを提供する必要があります。これにより、アプリケーションはPusherをクロスドメインリクエスト用に適切に設定されたaxiosインスタンスを使用するように設定できます。
1window.Echo = new Echo({ 2 broadcaster: "pusher", 3 cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, 4 encrypted: true, 5 key: import.meta.env.VITE_PUSHER_APP_KEY, 6 authorizer: (channel, options) => { 7 return { 8 authorize: (socketId, callback) => { 9 axios.post('/api/broadcasting/auth', {10 socket_id: socketId,11 channel_name: channel.name12 })13 .then(response => {14 callback(false, response.data);15 })16 .catch(error => {17 callback(true, error);18 });19 }20 };21 },22})
モバイルアプリケーション認証
Sanctumトークンを使用して、モバイルアプリケーションからAPIへのリクエストを認証することもできます。モバイルアプリケーションのリクエストを認証するプロセスは、サードパーティのAPIリクエストを認証するプロセスと似ていますが、APIトークンを発行する方法にわずかな違いがあります。
APIトークンの発行
まず、ユーザーのメールアドレス/ユーザー名、パスワード、およびデバイス名を受け入れ、それらの資格情報を新しいSanctumトークンと交換するルートを作成します。このエンドポイントに与えられる「デバイス名」は情報提供目的であり、任意の値にできます。一般的に、デバイス名の値はユーザーが認識できる名前、たとえば「Nuno's iPhone 12」などであるべきです。
通常、モバイルアプリケーションの「ログイン」画面からトークンエンドポイントにリクエストを送信します。エンドポイントはプレーンテキストのAPIトークンを返し、これをモバイルデバイスに保存して追加のAPIリクエストを行うために使用できます。
1use App\Models\User; 2use Illuminate\Http\Request; 3use Illuminate\Support\Facades\Hash; 4use Illuminate\Validation\ValidationException; 5 6Route::post('/sanctum/token', function (Request $request) { 7 $request->validate([ 8 'email' => 'required|email', 9 'password' => 'required',10 'device_name' => 'required',11 ]);12 13 $user = User::where('email', $request->email)->first();14 15 if (! $user || ! Hash::check($request->password, $user->password)) {16 throw ValidationException::withMessages([17 'email' => ['The provided credentials are incorrect.'],18 ]);19 }20 21 return $user->createToken($request->device_name)->plainTextToken;22});
モバイルアプリケーションがトークンを使用してアプリケーションにAPIリクエストを行う場合、トークンをAuthorizationヘッダにBearerトークンとして渡す必要があります。
モバイルアプリケーション用にトークンを発行する場合、トークンアビリティを自由に指定することもできます。
ルートの保護
以前に説明したように、ルートにsanctum認証ガードをアタッチすることで、すべての受信リクエストが認証されるようにルートを保護できます。
1Route::get('/user', function (Request $request) {2 return $request->user();3})->middleware('auth:sanctum');
トークンの無効化
ユーザーがモバイルデバイスに発行されたAPIトークンを無効にできるようにするには、WebアプリケーションのUIの「アカウント設定」部分で、名前と「無効化」ボタンを付けてリスト表示できます。ユーザーが「無効化」ボタンをクリックすると、データベースからトークンを削除できます。Laravel\Sanctum\HasApiTokensトレイトが提供するtokensリレーションシップを介してユーザーのAPIトークンにアクセスできることを忘れないでください。
1// Revoke all tokens...2$user->tokens()->delete();3 4// Revoke a specific token...5$user->tokens()->where('id', $tokenId)->delete();
テスト
テスト中、Sanctum::actingAsメソッドを使用してユーザーを認証し、トークンに付与されるアビリティを指定できます。
1use App\Models\User; 2use Laravel\Sanctum\Sanctum; 3 4test('task list can be retrieved', function () { 5 Sanctum::actingAs( 6 User::factory()->create(), 7 ['view-tasks'] 8 ); 9 10 $response = $this->get('/api/task');11 12 $response->assertOk();13});
1use App\Models\User; 2use Laravel\Sanctum\Sanctum; 3 4public function test_task_list_can_be_retrieved(): void 5{ 6 Sanctum::actingAs( 7 User::factory()->create(), 8 ['view-tasks'] 9 );10 11 $response = $this->get('/api/task');12 13 $response->assertOk();14}
トークンにすべてのアビリティを付与したい場合は、actingAsメソッドに提供するアビリティリストに*を含める必要があります。
1Sanctum::actingAs(2 User::factory()->create(),3 ['*']4);