コンテンツへスキップ

Laravel Passport

イントロダクション

Laravel Passportは、Laravelアプリケーションに完全なOAuth2サーバー実装を数分で提供します。PassportはAndy MillingtonとSimon HampによってメンテナンスされているLeague OAuth2 serverをベースに構築されています。

このドキュメントは、あなたがすでにOAuth2に精通していることを前提としています。OAuth2について何も知らない場合は、続きを読む前にOAuth2の一般的な用語や機能について理解を深めておくことを検討してください。

PassportかSanctumか

始める前に、アプリケーションがLaravel PassportとLaravel Sanctumのどちらに適しているかを判断するとよいでしょう。アプリケーションがOAuth2をサポートする必要がある場合は、Laravel Passportを使用する必要があります。

しかし、シングルページアプリケーションやモバイルアプリケーションの認証、APIトークンの発行を試みている場合は、Laravel Sanctumを使用すべきです。Laravel SanctumはOAuth2をサポートしていませんが、よりシンプルなAPI認証の開発体験を提供します。

インストール

install:api ArtisanコマンドでLaravel Passportをインストールできます。

1php artisan install:api --passport

このコマンドは、アプリケーションがOAuth2クライアントとアクセストークンを保存するために必要なテーブルを作成するためのデータベースマイグレーションを公開し、実行します。このコマンドは、安全なアクセストークンを生成するために必要な暗号化キーも作成します。

さらに、このコマンドは、PassportのClientモデルの主キー値として自動インクリメント整数ではなくUUIDを使用するかどうかを尋ねます。

install:apiコマンドを実行した後、App\Models\UserモデルにLaravel\Passport\HasApiTokensトレイトを追加してください。このトレイトは、認証済みユーザーのトークンとスコープを検査できるいくつかのヘルパーメソッドをモデルに提供します。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Foundation\Auth\User as Authenticatable;
7use Illuminate\Notifications\Notifiable;
8use Laravel\Passport\HasApiTokens;
9 
10class User extends Authenticatable
11{
12 use HasApiTokens, HasFactory, Notifiable;
13}

最後に、アプリケーションのconfig/auth.php設定ファイルで、api認証ガードを定義し、driverオプションをpassportに設定する必要があります。これにより、アプリケーションは受信APIリクエストを認証する際にPassportのTokenGuardを使用するように指示されます。

1'guards' => [
2 'web' => [
3 'driver' => 'session',
4 'provider' => 'users',
5 ],
6 
7 'api' => [
8 'driver' => 'passport',
9 'provider' => 'users',
10 ],
11],

Passportのデプロイ

Passportをアプリケーションのサーバーに初めてデプロイする際には、おそらくpassport:keysコマンドを実行する必要があります。このコマンドは、Passportがアクセストークンを生成するために必要な暗号化キーを生成します。生成されたキーは通常、ソース管理には保持しません。

1php artisan passport:keys

必要に応じて、Passportのキーをロードするパスを定義できます。これを行うにはPassport::loadKeysFromメソッドを使用します。通常、このメソッドはアプリケーションのApp\Providers\AppServiceProviderクラスのbootメソッドから呼び出すべきです。

1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 Passport::loadKeysFrom(__DIR__.'/../secrets/oauth');
7}

環境変数からのキーのロード

あるいは、vendor:publish Artisanコマンドを使用してPassportの設定ファイルを公開することもできます。

1php artisan vendor:publish --tag=passport-config

設定ファイルが公開された後、環境変数として定義することで、アプリケーションの暗号化キーをロードできます。

1PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
2<private key here>
3-----END RSA PRIVATE KEY-----"
4 
5PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
6<public key here>
7-----END PUBLIC KEY-----"

Passportのアップグレード

Passportの新しいメジャーバージョンにアップグレードする際には、アップグレードガイドを注意深く確認することが重要です。

設定

クライアントシークレットのハッシュ化

クライアントのシークレットをデータベースに保存する際にハッシュ化したい場合は、App\Providers\AppServiceProviderクラスのbootメソッドでPassport::hashClientSecretsメソッドを呼び出す必要があります。

1use Laravel\Passport\Passport;
2 
3Passport::hashClientSecrets();

有効にすると、すべてのクライアントシークレットは作成直後にのみユーザーに表示されるようになります。平文のクライアントシークレット値はデータベースに保存されないため、紛失した場合にシークレットの値を回復することはできません。

トークンの有効期間

デフォルトでは、Passportは1年後に失効する長期のアクセストークンを発行します。より長い、または短いトークンの有効期間を設定したい場合は、tokensExpireInrefreshTokensExpireInpersonalAccessTokensExpireInメソッドを使用できます。これらのメソッドは、アプリケーションのApp\Providers\AppServiceProviderクラスのbootメソッドから呼び出す必要があります。

1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 Passport::tokensExpireIn(now()->addDays(15));
7 Passport::refreshTokensExpireIn(now()->addDays(30));
8 Passport::personalAccessTokensExpireIn(now()->addMonths(6));
9}

Passportのデータベーステーブルのexpires_atカラムは読み取り専用であり、表示目的のみに使用されます。トークンを発行する際、Passportは有効期限情報を署名・暗号化されたトークン内に保存します。トークンを無効にする必要がある場合は、失効させる必要があります。

デフォルトモデルのオーバーライド

独自のモデルを定義し、対応するPassportモデルを拡張することで、Passportが内部で使用するモデルを自由に拡張できます。

1use Laravel\Passport\Client as PassportClient;
2 
3class Client extends PassportClient
4{
5 // ...
6}

モデルを定義した後、Laravel\Passport\Passportクラスを介してカスタムモデルを使用するようにPassportに指示できます。通常、カスタムモデルについては、アプリケーションのApp\Providers\AppServiceProviderクラスのbootメソッドでPassportに通知すべきです。

1use App\Models\Passport\AuthCode;
2use App\Models\Passport\Client;
3use App\Models\Passport\PersonalAccessClient;
4use App\Models\Passport\RefreshToken;
5use App\Models\Passport\Token;
6 
7/**
8 * Bootstrap any application services.
9 */
10public function boot(): void
11{
12 Passport::useTokenModel(Token::class);
13 Passport::useRefreshTokenModel(RefreshToken::class);
14 Passport::useAuthCodeModel(AuthCode::class);
15 Passport::useClientModel(Client::class);
16 Passport::usePersonalAccessClientModel(PersonalAccessClient::class);
17}

ルートの上書き

Passportによって定義されたルートをカスタマイズしたい場合があります。これを実現するには、まずアプリケーションのAppServiceProviderregisterメソッドにPassport::ignoreRoutesを追加して、Passportによって登録されたルートを無視する必要があります。

1use Laravel\Passport\Passport;
2 
3/**
4 * Register any application services.
5 */
6public function register(): void
7{
8 Passport::ignoreRoutes();
9}

次に、Passportのルートファイルで定義されているルートをアプリケーションのroutes/web.phpファイルにコピーし、好みに合わせて変更できます。

1Route::group([
2 'as' => 'passport.',
3 'prefix' => config('passport.path', 'oauth'),
4 'namespace' => '\Laravel\Passport\Http\Controllers',
5], function () {
6 // Passport routes...
7});

アクセストークンの発行

認可コードを介してOAuth2を使用する方法は、ほとんどの開発者がOAuth2に慣れ親しんでいる方法です。認可コードを使用する場合、クライアントアプリケーションはユーザーをあなたのサーバーにリダイレクトし、そこでユーザーはクライアントへのアクセストークンの発行要求を承認または拒否します。

クライアントの管理

まず、アプリケーションのAPIと対話する必要があるアプリケーションを構築する開発者は、「クライアント」を作成してアプリケーションを登録する必要があります。通常、これにはアプリケーションの名前と、ユーザーが認可要求を承認した後にアプリケーションがリダイレクトできるURLを提供することが含まれます。

passport:clientコマンド

クライアントを作成する最も簡単な方法は、passport:client Artisanコマンドを使用することです。このコマンドは、OAuth2機能をテストするための独自のクライアントを作成するために使用できます。clientコマンドを実行すると、Passportはクライアントに関する詳細情報を尋ね、クライアントIDとシークレットを提供します。

1php artisan passport:client

リダイレクトURL

クライアントに複数のリダイレクトURLを許可したい場合は、passport:clientコマンドでURLの入力を求められたときに、カンマ区切りのリストで指定できます。カンマを含むURLはURLエンコードする必要があります。

1http://example.com/callback,http://examplefoo.com/callback

JSON API

アプリケーションのユーザーはclientコマンドを利用できないため、Passportはクライアントを作成するために使用できるJSON APIを提供します。これにより、クライアントの作成、更新、削除のためのコントローラを手動でコーディングする手間が省けます。

ただし、PassportのJSON APIを独自のフロントエンドと組み合わせて、ユーザーがクライアントを管理するためのダッシュボードを提供する必要があります。以下では、クライアントを管理するためのすべてのAPIエンドポイントを確認します。便宜上、エンドポイントへのHTTPリクエストのデモンストレーションにはAxiosを使用します。

JSON APIはwebおよびauthミドルウェアによって保護されているため、独自のアプリケーションからのみ呼び出すことができます。外部ソースから呼び出すことはできません。

GET /oauth/clients

このルートは、認証されたユーザーのすべてのクライアントを返します。これは主に、ユーザーが編集または削除できるように、すべてのユーザークライアントを一覧表示するのに役立ちます。

1axios.get('/oauth/clients')
2 .then(response => {
3 console.log(response.data);
4 });

POST /oauth/clients

このルートは新しいクライアントを作成するために使用されます。クライアントのnameredirect URLの2つのデータが必要です。redirect URLは、ユーザーが認可要求を承認または拒否した後にリダイレクトされる場所です。

クライアントが作成されると、クライアントIDとクライアントシークレットが発行されます。これらの値は、アプリケーションからアクセストークンを要求するときに使用されます。クライアント作成ルートは、新しいクライアントインスタンスを返します。

1const data = {
2 name: 'Client Name',
3 redirect: 'http://example.com/callback'
4};
5 
6axios.post('/oauth/clients', data)
7 .then(response => {
8 console.log(response.data);
9 })
10 .catch (response => {
11 // List errors on response...
12 });

PUT /oauth/clients/{client-id}

このルートはクライアントを更新するために使用されます。クライアントのnameredirect URLの2つのデータが必要です。redirect URLは、ユーザーが認可要求を承認または拒否した後にリダイレクトされる場所です。このルートは更新されたクライアントインスタンスを返します。

1const data = {
2 name: 'New Client Name',
3 redirect: 'http://example.com/callback'
4};
5 
6axios.put('/oauth/clients/' + clientId, data)
7 .then(response => {
8 console.log(response.data);
9 })
10 .catch (response => {
11 // List errors on response...
12 });

DELETE /oauth/clients/{client-id}

このルートはクライアントを削除するために使用されます。

1axios.delete('/oauth/clients/' + clientId)
2 .then(response => {
3 // ...
4 });

トークンのリクエスト

認可のためのリダイレクト

クライアントが作成されると、開発者はクライアントIDとシークレットを使用して、アプリケーションから認可コードとアクセストークンを要求できます。まず、利用側アプリケーションは、次のようにアプリケーションの/oauth/authorizeルートにリダイレクト要求を行う必要があります。

1use Illuminate\Http\Request;
2use Illuminate\Support\Str;
3 
4Route::get('/redirect', function (Request $request) {
5 $request->session()->put('state', $state = Str::random(40));
6 
7 $query = http_build_query([
8 'client_id' => 'client-id',
9 'redirect_uri' => 'http://third-party-app.com/callback',
10 'response_type' => 'code',
11 'scope' => '',
12 'state' => $state,
13 // 'prompt' => '', // "none", "consent", or "login"
14 ]);
15 
16 return redirect('http://passport-app.test/oauth/authorize?'.$query);
17});

promptパラメータを使用して、Passportアプリケーションの認証動作を指定できます。

promptの値がnoneの場合、ユーザーがPassportアプリケーションでまだ認証されていない場合は、Passportは常に認証エラーをスローします。値がconsentの場合、Passportは、すべてのスコープが以前に利用側アプリケーションに付与されていた場合でも、常に認可承認画面を表示します。値がloginの場合、Passportアプリケーションは、既存のセッションがある場合でも、ユーザーに常にアプリケーションへの再ログインを促します。

promptの値が指定されていない場合、ユーザーは、要求されたスコープについて利用側アプリケーションへのアクセスを以前に認可していない場合にのみ、認可を求められます。

/oauth/authorizeルートはすでにPassportによって定義されていることを忘れないでください。このルートを手動で定義する必要はありません。

リクエストの承認

認可リクエストを受信すると、Passportはpromptパラメータの値(存在する場合)に基づいて自動的に応答し、ユーザーが認可リクエストを承認または拒否できるテンプレートを表示することがあります。リクエストを承認すると、利用側アプリケーションによって指定されたredirect_uriにリダイレクトされます。redirect_uriは、クライアントが作成されたときに指定されたredirect URLと一致する必要があります。

認可承認画面をカスタマイズしたい場合は、vendor:publish Artisanコマンドを使用してPassportのビューを公開できます。公開されたビューはresources/views/vendor/passportディレクトリに配置されます。

1php artisan vendor:publish --tag=passport-views

ファーストパーティクライアントを認可する場合など、認可プロンプトをスキップしたい場合があります。これは、Clientモデルを拡張し、skipsAuthorizationメソッドを定義することで実現できます。skipsAuthorizationtrueを返す場合、クライアントは承認され、利用側アプリケーションが認可のためのリダイレクト時にpromptパラメータを明示的に設定していない限り、ユーザーはすぐにredirect_uriにリダイレクトされます。

1<?php
2 
3namespace App\Models\Passport;
4 
5use Laravel\Passport\Client as BaseClient;
6 
7class Client extends BaseClient
8{
9 /**
10 * Determine if the client should skip the authorization prompt.
11 */
12 public function skipsAuthorization(): bool
13 {
14 return $this->firstParty();
15 }
16}

認可コードからアクセストークンへの変換

ユーザーが認可リクエストを承認すると、利用側アプリケーションにリダイレクトされます。利用側はまず、リダイレクト前に保存された値とstateパラメータを検証する必要があります。stateパラメータが一致する場合、利用側はアプリケーションにPOSTリクエストを発行してアクセストークンを要求する必要があります。リクエストには、ユーザーが認可リクエストを承認したときにアプリケーションによって発行された認可コードを含める必要があります。

1use Illuminate\Http\Request;
2use Illuminate\Support\Facades\Http;
3 
4Route::get('/callback', function (Request $request) {
5 $state = $request->session()->pull('state');
6 
7 throw_unless(
8 strlen($state) > 0 && $state === $request->state,
9 InvalidArgumentException::class,
10 'Invalid state value.'
11 );
12 
13 $response = Http::asForm()->post('http://passport-app.test/oauth/token', [
14 'grant_type' => 'authorization_code',
15 'client_id' => 'client-id',
16 'client_secret' => 'client-secret',
17 'redirect_uri' => 'http://third-party-app.com/callback',
18 'code' => $request->code,
19 ]);
20 
21 return $response->json();
22});

この/oauth/tokenルートは、access_tokenrefresh_token、およびexpires_in属性を含むJSONレスポンスを返します。expires_in属性には、アクセストークンが失効するまでの秒数が含まれます。

/oauth/authorizeルートと同様に、/oauth/tokenルートもPassportによって定義されています。このルートを手動で定義する必要はありません。

JSON API

Passportには、認可済みアクセストークンを管理するためのJSON APIも含まれています。これを独自のフロントエンドと組み合わせて、ユーザーにアクセストークンを管理するためのダッシュボードを提供できます。便宜上、エンドポイントへのHTTPリクエストのデモンストレーションにはAxiosを使用します。JSON APIはwebおよびauthミドルウェアによって保護されているため、独自のアプリケーションからのみ呼び出すことができます。

GET /oauth/tokens

このルートは、認証されたユーザーが作成したすべての認可済みアクセストークンを返します。これは主に、ユーザーがトークンを失効できるように、すべてのユーザートークンを一覧表示するのに役立ちます。

1axios.get('/oauth/tokens')
2 .then(response => {
3 console.log(response.data);
4 });

DELETE /oauth/tokens/{token-id}

このルートは、認可済みアクセストークンとそれに関連するリフレッシュトークンを失効させるために使用できます。

1axios.delete('/oauth/tokens/' + tokenId);

トークンのリフレッシュ

アプリケーションが短期のアクセストークンを発行する場合、ユーザーはアクセストークンが発行されたときに提供されたリフレッシュトークンを介してアクセストークンをリフレッシュする必要があります。

1use Illuminate\Support\Facades\Http;
2 
3$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
4 'grant_type' => 'refresh_token',
5 'refresh_token' => 'the-refresh-token',
6 'client_id' => 'client-id',
7 'client_secret' => 'client-secret',
8 'scope' => '',
9]);
10 
11return $response->json();

この/oauth/tokenルートは、access_tokenrefresh_token、およびexpires_in属性を含むJSONレスポンスを返します。expires_in属性には、アクセストークンが失効するまでの秒数が含まれます。

トークンの失効

Laravel\Passport\TokenRepositoryrevokeAccessTokenメソッドを使用してトークンを失効させることができます。Laravel\Passport\RefreshTokenRepositoryrevokeRefreshTokensByAccessTokenIdメソッドを使用してトークンのリフレッシュトークンを失効させることができます。これらのクラスは、Laravelのサービスコンテナを使用して解決できます。

1use Laravel\Passport\TokenRepository;
2use Laravel\Passport\RefreshTokenRepository;
3 
4$tokenRepository = app(TokenRepository::class);
5$refreshTokenRepository = app(RefreshTokenRepository::class);
6 
7// Revoke an access token...
8$tokenRepository->revokeAccessToken($tokenId);
9 
10// Revoke all of the token's refresh tokens...
11$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);

トークンのクリーンアップ

トークンが失効または期限切れになった場合、データベースからそれらをクリーンアップしたい場合があります。Passportに含まれるpassport:purge Artisanコマンドでこれを実行できます。

1# Purge revoked and expired tokens and auth codes...
2php artisan passport:purge
3 
4# Only purge tokens expired for more than 6 hours...
5php artisan passport:purge --hours=6
6 
7# Only purge revoked tokens and auth codes...
8php artisan passport:purge --revoked
9 
10# Only purge expired tokens and auth codes...
11php artisan passport:purge --expired

アプリケーションのroutes/console.phpファイルでスケジュールされたジョブを設定して、スケジュールに従ってトークンを自動的にクリーンアップすることもできます。

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('passport:purge')->hourly();

PKCE付き認可コードグラント

「Proof Key for Code Exchange」(PKCE)付きの認可コードグラントは、シングルページアプリケーションやネイティブアプリケーションがAPIにアクセスするために安全に認証するための方法です。このグラントは、クライアントシークレットが機密に保存されることを保証できない場合や、攻撃者によって認可コードが傍受される脅威を軽減するために使用すべきです。「コードベリファイア」と「コードチャレンジ」の組み合わせが、認可コードをアクセストークンと交換する際にクライアントシークレットの代わりになります。

クライアントの作成

アプリケーションがPKCE付き認可コードグラントを介してトークンを発行する前に、PKCE対応クライアントを作成する必要があります。これは、passport:client Artisanコマンドに--publicオプションを付けて実行することで行えます。

1php artisan passport:client --public

トークンのリクエスト

コードベリファイアとコードチャレンジ

この認可グラントはクライアントシークレットを提供しないため、開発者はトークンを要求するためにコードベリファイアとコードチャレンジの組み合わせを生成する必要があります。

RFC 7636仕様で定義されているように、コードベリファイアは、文字、数字、および"-"".""_""~"文字を含む43から128文字のランダムな文字列である必要があります。

コードチャレンジは、URLおよびファイル名セーフな文字を使用したBase64エンコード文字列である必要があります。末尾の'='文字は削除し、改行、空白、その他の追加文字は存在しないようにしてください。

1$encoded = base64_encode(hash('sha256', $code_verifier, true));
2 
3$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');

認可のためのリダイレクト

クライアントが作成されたら、クライアントIDと生成されたコードベリファイアおよびコードチャレンジを使用して、アプリケーションから認可コードとアクセストークンを要求できます。まず、利用側アプリケーションは、アプリケーションの/oauth/authorizeルートにリダイレクト要求を行う必要があります。

1use Illuminate\Http\Request;
2use Illuminate\Support\Str;
3 
4Route::get('/redirect', function (Request $request) {
5 $request->session()->put('state', $state = Str::random(40));
6 
7 $request->session()->put(
8 'code_verifier', $code_verifier = Str::random(128)
9 );
10 
11 $codeChallenge = strtr(rtrim(
12 base64_encode(hash('sha256', $code_verifier, true))
13 , '='), '+/', '-_');
14 
15 $query = http_build_query([
16 'client_id' => 'client-id',
17 'redirect_uri' => 'http://third-party-app.com/callback',
18 'response_type' => 'code',
19 'scope' => '',
20 'state' => $state,
21 'code_challenge' => $codeChallenge,
22 'code_challenge_method' => 'S256',
23 // 'prompt' => '', // "none", "consent", or "login"
24 ]);
25 
26 return redirect('http://passport-app.test/oauth/authorize?'.$query);
27});

認可コードからアクセストークンへの変換

ユーザーが認可リクエストを承認すると、利用側アプリケーションにリダイレクトされます。利用側は、標準の認可コードグラントと同様に、リダイレクト前に保存された値とstateパラメータを検証する必要があります。

stateパラメータが一致する場合、利用側はアプリケーションにPOSTリクエストを発行してアクセストークンを要求する必要があります。リクエストには、ユーザーが認可リクエストを承認したときにアプリケーションによって発行された認可コードと、最初に生成されたコードベリファイアを含める必要があります。

1use Illuminate\Http\Request;
2use Illuminate\Support\Facades\Http;
3 
4Route::get('/callback', function (Request $request) {
5 $state = $request->session()->pull('state');
6 
7 $codeVerifier = $request->session()->pull('code_verifier');
8 
9 throw_unless(
10 strlen($state) > 0 && $state === $request->state,
11 InvalidArgumentException::class
12 );
13 
14 $response = Http::asForm()->post('http://passport-app.test/oauth/token', [
15 'grant_type' => 'authorization_code',
16 'client_id' => 'client-id',
17 'redirect_uri' => 'http://third-party-app.com/callback',
18 'code_verifier' => $codeVerifier,
19 'code' => $request->code,
20 ]);
21 
22 return $response->json();
23});

パスワードグラントトークン

パスワードグラントトークンの使用はもはや推奨しません。代わりに、OAuth2 Serverが現在推奨しているグラントタイプを選択してください。

OAuth2パスワードグラントを使用すると、モバイルアプリケーションなどの他のファーストパーティクライアントが、メールアドレス/ユーザー名とパスワードを使用してアクセストークンを取得できます。これにより、ユーザーに完全なOAuth2認可コードリダイレクトフローを経由させることなく、ファーストパーティクライアントに安全にアクセストークンを発行できます。

パスワードグラントを有効にするには、アプリケーションのApp\Providers\AppServiceProviderクラスのbootメソッドでenablePasswordGrantメソッドを呼び出します。

1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 Passport::enablePasswordGrant();
7}

パスワードグラントクライアントの作成

アプリケーションがパスワードグラントを介してトークンを発行する前に、パスワードグラントクライアントを作成する必要があります。これは、passport:client Artisanコマンドに--passwordオプションを付けて実行することで行えます。すでにpassport:installコマンドを実行している場合は、このコマンドを実行する必要はありません:

1php artisan passport:client --password

トークンのリクエスト

パスワードグラントクライアントを作成したら、ユーザーのメールアドレスとパスワードを付けて/oauth/tokenルートにPOSTリクエストを発行することでアクセストークンを要求できます。このルートはPassportによってすでに登録されているため、手動で定義する必要はありません。リクエストが成功すると、サーバーからのJSONレスポンスでaccess_tokenrefresh_tokenを受け取ります。

1use Illuminate\Support\Facades\Http;
2 
3$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
4 'grant_type' => 'password',
5 'client_id' => 'client-id',
6 'client_secret' => 'client-secret',
7 'username' => '[email protected]',
8 'password' => 'my-password',
9 'scope' => '',
10]);
11 
12return $response->json();

アクセストークンはデフォルトで長期有効であることに注意してください。ただし、必要に応じて最大アクセストークン有効期間を自由に設定できます。

すべてのスコープのリクエスト

パスワードグラントまたはクライアントクレデンシャルグラントを使用する場合、アプリケーションでサポートされているすべてのスコープに対してトークンを認可したい場合があります。これは、*スコープをリクエストすることで行えます。*スコープをリクエストすると、トークンインスタンスのcanメソッドは常にtrueを返します。このスコープは、passwordまたはclient_credentialsグラントを使用して発行されたトークンにのみ割り当てることができます。

1use Illuminate\Support\Facades\Http;
2 
3$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
4 'grant_type' => 'password',
5 'client_id' => 'client-id',
6 'client_secret' => 'client-secret',
7 'username' => '[email protected]',
8 'password' => 'my-password',
9 'scope' => '*',
10]);

ユーザープロバイダのカスタマイズ

アプリケーションが複数の認証ユーザープロバイダを使用している場合、artisan passport:client --passwordコマンドでクライアントを作成する際に--providerオプションを指定することで、パスワードグラントクライアントが使用するユーザープロバイダを指定できます。指定されたプロバイダ名は、アプリケーションのconfig/auth.php設定ファイルで定義されている有効なプロバイダと一致する必要があります。その後、ミドルウェアを使用してルートを保護し、ガードで指定されたプロバイダのユーザーのみが認可されるようにできます。

ユーザー名フィールドのカスタマイズ

パスワードグラントを使用して認証する場合、Passportは認証可能なモデルのemail属性を「ユーザー名」として使用します。ただし、モデルにfindForPassportメソッドを定義することで、この動作をカスタマイズできます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7use Laravel\Passport\HasApiTokens;
8 
9class User extends Authenticatable
10{
11 use HasApiTokens, Notifiable;
12 
13 /**
14 * Find the user instance for the given username.
15 */
16 public function findForPassport(string $username): User
17 {
18 return $this->where('username', $username)->first();
19 }
20}

パスワード検証のカスタマイズ

パスワードグラントを使用して認証する場合、Passportはモデルのpassword属性を使用して指定されたパスワードを検証します。モデルにpassword属性がない場合や、パスワード検証ロジックをカスタマイズしたい場合は、モデルにvalidateForPassportPasswordGrantメソッドを定義できます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7use Illuminate\Support\Facades\Hash;
8use Laravel\Passport\HasApiTokens;
9 
10class User extends Authenticatable
11{
12 use HasApiTokens, Notifiable;
13 
14 /**
15 * Validate the password of the user for the Passport password grant.
16 */
17 public function validateForPassportPasswordGrant(string $password): bool
18 {
19 return Hash::check($password, $this->password);
20 }
21}

インプリシットグラントトークン

インプリシットグラントトークンの使用はもはや推奨しません。代わりに、OAuth2 Serverが現在推奨しているグラントタイプを選択してください。

インプリシットグラントは認可コードグラントに似ていますが、認可コードを交換することなくトークンがクライアントに返されます。このグラントは、クライアントクレデンシャルを安全に保存できないJavaScriptやモバイルアプリケーションで最も一般的に使用されます。このグラントを有効にするには、アプリケーションのApp\Providers\AppServiceProviderクラスのbootメソッドでenableImplicitGrantメソッドを呼び出します。

1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 Passport::enableImplicitGrant();
7}

グラントが有効になったら、開発者はクライアントIDを使用してアプリケーションからアクセストークンを要求できます。利用側アプリケーションは、次のようにアプリケーションの/oauth/authorizeルートにリダイレクト要求を行う必要があります。

1use Illuminate\Http\Request;
2 
3Route::get('/redirect', function (Request $request) {
4 $request->session()->put('state', $state = Str::random(40));
5 
6 $query = http_build_query([
7 'client_id' => 'client-id',
8 'redirect_uri' => 'http://third-party-app.com/callback',
9 'response_type' => 'token',
10 'scope' => '',
11 'state' => $state,
12 // 'prompt' => '', // "none", "consent", or "login"
13 ]);
14 
15 return redirect('http://passport-app.test/oauth/authorize?'.$query);
16});

/oauth/authorizeルートはすでにPassportによって定義されていることを忘れないでください。このルートを手動で定義する必要はありません。

クライアントクレデンシャルグラントトークン

クライアントクレデンシャルグラントは、マシン間の認証に適しています。たとえば、APIを介してメンテナンスタスクを実行するスケジュールされたジョブでこのグラントを使用できます。

アプリケーションがクライアントクレデンシャルグラントを介してトークンを発行する前に、クライアントクレデンシャルグラントクライアントを作成する必要があります。これは、passport:client Artisanコマンドの--clientオプションを使用して行えます。

1php artisan passport:client --client

次に、このグラントタイプを使用するには、CheckClientCredentialsミドルウェアのミドルウェアエイリアスを登録します。ミドルウェアエイリアスは、アプリケーションのbootstrap/app.phpファイルで定義できます。

1use Laravel\Passport\Http\Middleware\CheckClientCredentials;
2 
3->withMiddleware(function (Middleware $middleware) {
4 $middleware->alias([
5 'client' => CheckClientCredentials::class
6 ]);
7})

次に、ミドルウェアをルートにアタッチします。

1Route::get('/orders', function (Request $request) {
2 // ...
3})->middleware('client');

ルートへのアクセスを特定のスコープに制限するには、clientミドルウェアをルートにアタッチする際に、必要なスコープのカンマ区切りリストを指定します。

1Route::get('/orders', function (Request $request) {
2 // ...
3})->middleware('client:check-status,your-scope');

トークンの取得

このグラントタイプを使用してトークンを取得するには、oauth/tokenエンドポイントにリクエストを送信します。

1use Illuminate\Support\Facades\Http;
2 
3$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
4 'grant_type' => 'client_credentials',
5 'client_id' => 'client-id',
6 'client_secret' => 'client-secret',
7 'scope' => 'your-scope',
8]);
9 
10return $response->json()['access_token'];

パーソナルアクセストークン

ユーザーは、通常の認可コードリダイレクトフローを経由せずに、自分自身にアクセストークンを発行したい場合があります。アプリケーションのUIを介してユーザーが自分自身にトークンを発行できるようにすることは、ユーザーがAPIを試すことを許可したり、一般的にアクセストークンを発行するより簡単なアプローチとして役立ちます。

アプリケーションが主にパーソナルアクセストークンを発行するためにPassportを使用している場合は、APIアクセストークンを発行するためのLaravelの軽量なファーストパーティライブラリであるLaravel Sanctumの使用を検討してください。

パーソナルアクセスクライアントの作成

アプリケーションがパーソナルアクセストークンを発行する前に、パーソナルアクセスクライアントを作成する必要があります。これを行うには、passport:client Artisanコマンドに--personalオプションを付けて実行します。すでにpassport:installコマンドを実行している場合は、このコマンドを実行する必要はありません。

1php artisan passport:client --personal

パーソナルアクセスクライアントを作成した後、クライアントのIDと平文のシークレット値をアプリケーションの.envファイルに配置します。

1PASSPORT_PERSONAL_ACCESS_CLIENT_ID="client-id-value"
2PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET="unhashed-client-secret-value"

パーソナルアクセストークンの管理

パーソナルアクセス クライアントを作成したら、App\Models\User モデル インスタンスの createToken メソッドを使用して、特定のユーザーのトークンを発行できます。createToken メソッドは、最初の引数としてトークンの名前を受け取り、2 番目の引数としてオプションのスコープの配列を受け入れます。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5// Creating a token without scopes...
6$token = $user->createToken('Token Name')->accessToken;
7 
8// Creating a token with scopes...
9$token = $user->createToken('My Token', ['place-orders'])->accessToken;

JSON API

Passportには、パーソナルアクセストークンを管理するためのJSON APIも含まれています。これを独自のフロントエンドと組み合わせて、ユーザーにパーソナルアクセストークンを管理するためのダッシュボードを提供できます。以下では、パーソナルアクセストークンを管理するためのすべてのAPIエンドポイントを確認します。便宜上、エンドポイントへのHTTPリクエストのデモンストレーションにはAxiosを使用します。

JSON APIはwebおよびauthミドルウェアによって保護されているため、独自のアプリケーションからのみ呼び出すことができます。外部ソースから呼び出すことはできません。

GET /oauth/scopes

このルートは、アプリケーションに定義されているすべてのスコープを返します。このルートを使用して、ユーザーがパーソナルアクセストークンに割り当てることができるスコープを一覧表示できます。

1axios.get('/oauth/scopes')
2 .then(response => {
3 console.log(response.data);
4 });

GET /oauth/personal-access-tokens

このルートは、認証されたユーザーが作成したすべてのパーソナルアクセストークンを返します。これは主に、ユーザーがトークンを編集または失効できるように、すべてのユーザートークンを一覧表示するのに役立ちます。

1axios.get('/oauth/personal-access-tokens')
2 .then(response => {
3 console.log(response.data);
4 });

POST /oauth/personal-access-tokens

このルートは新しいパーソナルアクセストークンを作成します。トークンのnameと、トークンに割り当てるべきscopesの2つのデータが必要です。

1const data = {
2 name: 'Token Name',
3 scopes: []
4};
5 
6axios.post('/oauth/personal-access-tokens', data)
7 .then(response => {
8 console.log(response.data.accessToken);
9 })
10 .catch (response => {
11 // List errors on response...
12 });

DELETE /oauth/personal-access-tokens/{token-id}

このルートは、パーソナルアクセストークンを失効させるために使用できます。

1axios.delete('/oauth/personal-access-tokens/' + tokenId);

ルートの保護

ミドルウェアによる保護

Passportには、受信リクエストのアクセストークンを検証する認証ガードが含まれています。apiガードがpassportドライバを使用するように設定したら、有効なアクセストークンを必要とするすべてのルートにauth:apiミドルウェアを指定するだけです。

1Route::get('/user', function () {
2 // ...
3})->middleware('auth:api');

クライアントクレデンシャルグラントを使用している場合は、auth:apiミドルウェアの代わりにclientミドルウェアを使用してルートを保護する必要があります。

複数の認証ガード

アプリケーションが、おそらくまったく異なるEloquentモデルを使用するさまざまなタイプのユーザーを認証する場合、アプリケーション内の各ユーザープロバイダタイプごとにガード設定を定義する必要があるでしょう。これにより、特定のユーザープロバイダ向けの要求を保護できます。たとえば、config/auth.php設定ファイルに以下のガード設定があるとします。

1'api' => [
2 'driver' => 'passport',
3 'provider' => 'users',
4],
5 
6'api-customers' => [
7 'driver' => 'passport',
8 'provider' => 'customers',
9],

次のルートは、customersユーザープロバイダを使用するapi-customersガードを利用して、受信リクエストを認証します。

1Route::get('/customer', function () {
2 // ...
3})->middleware('auth:api-customers');

Passportで複数のユーザープロバイダを使用する方法の詳細については、パスワードグラントのドキュメントを参照してください。

アクセストークンの受け渡し

Passportによって保護されているルートを呼び出すとき、アプリケーションのAPI利用者は、リクエストのAuthorizationヘッダーにアクセストークンをBearerトークンとして指定する必要があります。たとえば、Guzzle HTTPライブラリを使用する場合:

1use Illuminate\Support\Facades\Http;
2 
3$response = Http::withHeaders([
4 'Accept' => 'application/json',
5 'Authorization' => 'Bearer '.$accessToken,
6])->get('https://passport-app.test/api/user');
7 
8return $response->json();

トークンスコープ

スコープを使用すると、APIクライアントはアカウントへのアクセスを認可してもらう際に、特定の権限セットを要求できます。たとえば、eコマースアプリケーションを構築している場合、すべてのAPI利用者が注文を行う機能を必要とするわけではありません。代わりに、利用者が注文の配送状況へのアクセスのみを要求できるようにすることができます。言い換えれば、スコープを使用すると、アプリケーションのユーザーはサードパーティアプリケーションが自分たちの代わりに行えるアクションを制限できます。

スコープの定義

APIのスコープは、アプリケーションのApp\Providers\AppServiceProviderクラスのbootメソッドでPassport::tokensCanメソッドを使用して定義できます。tokensCanメソッドは、スコープ名とスコープの説明の配列を受け入れます。スコープの説明は任意のものでよく、認可承認画面でユーザーに表示されます。

1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 Passport::tokensCan([
7 'place-orders' => 'Place orders',
8 'check-status' => 'Check order status',
9 ]);
10}

デフォルトスコープ

クライアントが特定のスコープを要求しない場合、setDefaultScopeメソッドを使用して、Passportサーバーがデフォルトのスコープをトークンにアタッチするように設定できます。通常、このメソッドはアプリケーションのApp\Providers\AppServiceProviderクラスのbootメソッドから呼び出すべきです。

1use Laravel\Passport\Passport;
2 
3Passport::tokensCan([
4 'place-orders' => 'Place orders',
5 'check-status' => 'Check order status',
6]);
7 
8Passport::setDefaultScope([
9 'check-status',
10 'place-orders',
11]);

Passportのデフォルトスコープは、ユーザーによって生成されたパーソナルアクセストークンには適用されません。

トークンへのスコープの割り当て

認可コードをリクエストする場合

認可コードグラントを使用してアクセストークンを要求する場合、利用者は希望するスコープをscopeクエリ文字列パラメータとして指定する必要があります。scopeパラメータは、スペースで区切られたスコープのリストである必要があります。

1Route::get('/redirect', function () {
2 $query = http_build_query([
3 'client_id' => 'client-id',
4 'redirect_uri' => 'http://example.com/callback',
5 'response_type' => 'code',
6 'scope' => 'place-orders check-status',
7 ]);
8 
9 return redirect('http://passport-app.test/oauth/authorize?'.$query);
10});

パーソナルアクセストークンを発行する場合

App\Models\UserモデルのcreateTokenメソッドを使用してパーソナルアクセストークンを発行する場合、希望するスコープの配列をメソッドの第2引数として渡すことができます。

1$token = $user->createToken('My Token', ['place-orders'])->accessToken;

スコープのチェック

Passportには、受信リクエストが特定のスコープを付与されたトークンで認証されていることを確認するために使用できる2つのミドルウェアが含まれています。まず、アプリケーションのbootstrap/app.phpファイルに次のミドルウェアエイリアスを定義します。

1use Laravel\Passport\Http\Middleware\CheckForAnyScope;
2use Laravel\Passport\Http\Middleware\CheckScopes;
3 
4->withMiddleware(function (Middleware $middleware) {
5 $middleware->alias([
6 'scopes' => CheckScopes::class,
7 'scope' => CheckForAnyScope::class,
8 ]);
9})

すべてのスコープのチェック

scopesミドルウェアをルートに割り当てて、受信リクエストのアクセストークンがリストされたすべてのスコープを持っていることを確認できます。

1Route::get('/orders', function () {
2 // Access token has both "check-status" and "place-orders" scopes...
3})->middleware(['auth:api', 'scopes:check-status,place-orders']);

いずれかのスコープのチェック

scopeミドルウェアをルートに割り当てて、受信リクエストのアクセストークンがリストされたスコープの*少なくとも1つ*を持っていることを確認できます。

1Route::get('/orders', function () {
2 // Access token has either "check-status" or "place-orders" scope...
3})->middleware(['auth:api', 'scope:check-status,place-orders']);

トークンインスタンスでのスコープのチェック

アクセストークンで認証されたリクエストがアプリケーションに入った後でも、認証されたApp\Models\UserインスタンスのtokenCanメソッドを使用して、トークンが特定のスコープを持っているかどうかを確認できます。

1use Illuminate\Http\Request;
2 
3Route::get('/orders', function (Request $request) {
4 if ($request->user()->tokenCan('place-orders')) {
5 // ...
6 }
7});

追加のスコープメソッド

scopeIdsメソッドは、定義されているすべてのID/名前の配列を返します。

1use Laravel\Passport\Passport;
2 
3Passport::scopeIds();

scopesメソッドは、定義されているすべてのスコープをLaravel\Passport\Scopeのインスタンスの配列として返します。

1Passport::scopes();

scopesForメソッドは、指定されたID/名前に一致するLaravel\Passport\Scopeインスタンスの配列を返します。

1Passport::scopesFor(['place-orders', 'check-status']);

hasScopeメソッドを使用して、特定のスコープが定義されているかどうかを判断できます。

1Passport::hasScope('place-orders');

JavaScriptによるAPIの利用

APIを構築する際、JavaScriptアプリケーションから独自のAPIを利用できることは非常に便利です。このAPI開発のアプローチにより、独自のアプリケーションが世界と共有しているのと同じAPIを利用できます。同じAPIは、Webアプリケーション、モバイルアプリケーション、サードパーティアプリケーション、およびさまざまなパッケージマネージャーで公開する可能性のあるSDKによって利用できます。

通常、JavaScriptアプリケーションからAPIを利用したい場合、手動でアクセストークンをアプリケーションに送信し、各リクエストでそれを渡す必要があります。しかし、Passportにはこれを処理できるミドルウェアが含まれています。アプリケーションのbootstrap/app.phpファイル内のwebミドルウェアグループにCreateFreshApiTokenミドルウェアを追加するだけです。

1use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
2 
3->withMiddleware(function (Middleware $middleware) {
4 $middleware->web(append: [
5 CreateFreshApiToken::class,
6 ]);
7})

CreateFreshApiTokenミドルウェアがミドルウェアスタックの最後にリストされていることを確認してください。

このミドルウェアは、送信されるレスポンスにlaravel_tokenクッキーを添付します。このクッキーには、PassportがJavaScriptアプリケーションからのAPIリクエストを認証するために使用する暗号化されたJWTが含まれています。JWTの有効期間は、session.lifetime設定値と同じです。これで、ブラウザは後続のすべてのリクエストで自動的にクッキーを送信するため、アクセストークンを明示的に渡すことなくアプリケーションのAPIにリクエストを送信できます。

1axios.get('/api/user')
2 .then(response => {
3 console.log(response.data);
4 });

必要に応じて、Passport::cookieメソッドを使用してlaravel_tokenクッキーの名前をカスタマイズできます。通常、このメソッドはアプリケーションのApp\Providers\AppServiceProviderクラスのbootメソッドから呼び出すべきです。

1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 Passport::cookie('custom_name');
7}

CSRF保護

この認証方法を使用する場合、リクエストに有効なCSRFトークンヘッダーが含まれていることを確認する必要があります。デフォルトのLaravel JavaScriptスキャフォールディングにはAxiosインスタンスが含まれており、暗号化されたXSRF-TOKENクッキー値を自動的に使用して、同一オリジンリクエストでX-XSRF-TOKENヘッダーを送信します。

X-XSRF-TOKENの代わりにX-CSRF-TOKENヘッダーを送信することを選択した場合、csrf_token()によって提供される暗号化されていないトークンを使用する必要があります。

イベント

Passportは、アクセストークンとリフレッシュトークンを発行する際にイベントを発生させます。これらのイベントをリッスンして、データベース内の他のアクセストークンをクリーンアップまたは失効させることができます。

イベント名
Laravel\Passport\Events\AccessTokenCreated
Laravel\Passport\Events\RefreshTokenCreated

テスト

PassportのactingAsメソッドを使用して、現在認証されているユーザーとそのスコープを指定できます。actingAsメソッドに与えられる最初の引数はユーザーインスタンスで、2番目はユーザートークンに付与されるべきスコープの配列です。

1use App\Models\User;
2use Laravel\Passport\Passport;
3 
4test('servers can be created', function () {
5 Passport::actingAs(
6 User::factory()->create(),
7 ['create-servers']
8 );
9 
10 $response = $this->post('/api/create-server');
11 
12 $response->assertStatus(201);
13});
1use App\Models\User;
2use Laravel\Passport\Passport;
3 
4public function test_servers_can_be_created(): void
5{
6 Passport::actingAs(
7 User::factory()->create(),
8 ['create-servers']
9 );
10 
11 $response = $this->post('/api/create-server');
12 
13 $response->assertStatus(201);
14}

PassportのactingAsClientメソッドを使用して、現在認証されているクライアントとそのスコープを指定できます。actingAsClientメソッドに与えられる最初の引数はクライアントインスタンスで、2番目はクライアントのトークンに付与されるべきスコープの配列です。

1use Laravel\Passport\Client;
2use Laravel\Passport\Passport;
3 
4test('orders can be retrieved', function () {
5 Passport::actingAsClient(
6 Client::factory()->create(),
7 ['check-status']
8 );
9 
10 $response = $this->get('/api/orders');
11 
12 $response->assertStatus(200);
13});
1use Laravel\Passport\Client;
2use Laravel\Passport\Passport;
3 
4public function test_orders_can_be_retrieved(): void
5{
6 Passport::actingAsClient(
7 Client::factory()->create(),
8 ['check-status']
9 );
10 
11 $response = $this->get('/api/orders');
12 
13 $response->assertStatus(200);
14}

Laravelは最も生産的な方法です
ソフトウェアを構築、デプロイ、監視します。