はじめに

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 認証開発体験を提供します。

インストール

Laravel Passport は install:api Artisan コマンドを使用してインストールできます:

  1. php artisan install:api --passport

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

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

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

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

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

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

Passport のデプロイ

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

  1. php artisan passport:keys

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

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

環境からのキーの読み込み

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

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

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

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

Passport のアップグレード

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

設定

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

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

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

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

トークンの有効期限

デフォルトでは、Passport は 1 年後に期限切れになる長寿命のアクセストークンを発行します。より長い/短いトークンの有効期限を設定したい場合は、tokensExpireInrefreshTokensExpireIn、および personalAccessTokensExpireIn メソッドを使用できます。これらのメソッドは、アプリケーションの boot クラスの App\Providers\AppServiceProvider メソッドから呼び出されるべきです:

  1. /**
  2. * Bootstrap any application services.
  3. */
  4. public 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 モデルを拡張することで実現できます:

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

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

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

ルートのオーバーライド

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

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

次に、そのルートファイル で定義された Passport のルートをアプリケーションの routes/web.php ファイルにコピーし、好みに応じて修正できます:

  1. Route::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 とシークレットを提供します:

  1. php artisan passport:client

リダイレクト URL

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

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

JSON API

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

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

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

GET /oauth/clients

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

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

POST /oauth/clients

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

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

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

PUT /oauth/clients/{client-id}

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

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

DELETE /oauth/clients/{client-id}

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

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

トークンの要求

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

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

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

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 ディレクトリに配置されます:

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

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

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

認可コードをアクセストークンに変換する

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

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

この /oauth/token ルートは、access_tokenrefresh_token、および expires_in 属性を含む JSON 応答を返します。expires_in 属性には、アクセストークンが期限切れになるまでの秒数が含まれています。

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

JSON API

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

GET /oauth/tokens

このルートは、認証されたユーザーが作成したすべての承認されたアクセストークンを返します。これは、ユーザーがトークンを取り消すことができるようにするために主に役立ちます:

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

DELETE /oauth/tokens/{token-id}

このルートは、承認されたアクセストークンとその関連するリフレッシュトークンを取り消すために使用できます:

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

トークンのリフレッシュ

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

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

この /oauth/token ルートは、access_tokenrefresh_token、および expires_in 属性を含む JSON 応答を返します。expires_in 属性には、アクセストークンが期限切れになるまでの秒数が含まれています。

トークンの取り消し

revokeAccessToken メソッドを Laravel\Passport\TokenRepository で使用してトークンを取り消すことができます。revokeRefreshTokensByAccessTokenId メソッドを Laravel\Passport\RefreshTokenRepository で使用してトークンのリフレッシュトークンを取り消すことができます。これらのクラスは、Laravel の サービスコンテナ を使用して解決できます:

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

トークンの削除

トークンが取り消されたり期限切れになったりした場合、データベースから削除したい場合があります。Passport に含まれる passport:purge Artisan コマンドがこれを行うことができます:

  1. # 取り消されたトークンと期限切れのトークンおよび認可コードを削除...
  2. php artisan passport:purge
  3. # 6 時間以上期限切れのトークンのみを削除...
  4. php artisan passport:purge --hours=6
  5. # 取り消されたトークンと認可コードのみを削除...
  6. php artisan passport:purge --revoked
  7. # 期限切れのトークンと認可コードのみを削除...
  8. php artisan passport:purge --expired

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

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

PKCE を使用した認可コードグラント

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

クライアントの作成

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

  1. php artisan passport:client --public

トークンの要求

コード検証子とコードチャレンジ

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

コード検証子は、43 文字から 128 文字の間のランダムな文字列で、文字、数字、"-"".""_""~" 文字を含む必要があります。これは RFC 7636 仕様 で定義されています。

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

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

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

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

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

認可コードをアクセストークンに変換する

ユーザーが認可リクエストを承認すると、消費アプリケーションにリダイレクトされます。消費者は、リダイレクト前に保存された値に対して state パラメータを検証する必要があります。標準の認可コードグラントと同様に、状態パラメータが一致する場合、消費者はアプリケーションに POST リクエストを発行してアクセストークンを要求する必要があります。リクエストには、ユーザーが認可リクエストを承認したときにアプリケーションによって発行された認可コードと、元々生成されたコード検証子が含まれている必要があります:

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

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

もはやパスワードグラントトークンの使用を推奨していません。代わりに、OAuth2 サーバーによって現在推奨されているグラントタイプ を選択するべきです。

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

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

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

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

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

  1. php artisan passport:client --password

トークンの要求

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

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

アクセストークンはデフォルトで長寿命です。ただし、必要に応じて 最大アクセストークンの有効期限を設定する ことができます。

すべてのスコープの要求

パスワードグラントまたはクライアント資格情報グラントを使用する場合、アプリケーションがサポートするすべてのスコープに対してトークンを承認したい場合があります。これは、* スコープを要求することで実現できます。* スコープを要求すると、トークンインスタンスの can メソッドは常に true を返します。このスコープは、password または client_credentials グラントを使用して発行されたトークンにのみ割り当てることができます:

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

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

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

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

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

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

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

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

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

暗黙的グラントトークン

もはや暗黙的グラントトークンの使用を推奨していません。代わりに、OAuth2 サーバーによって現在推奨されているグラントタイプ を選択するべきです。

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

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

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

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

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

クライアント資格情報グラントトークン

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

アプリケーションがクライアント資格情報グラントを介してトークンを発行できるようにするには、--client Artisan コマンドの passport:client オプションを使用してクライアント資格情報グラントクライアントを作成する必要があります:

  1. php artisan passport:client --client

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

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

次に、ルートにミドルウェアを添付します:

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

ルートへのアクセスを特定のスコープに制限するには、client ミドルウェアをルートに添付する際に必要なスコープのカンマ区切りリストを提供できます:

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

トークンの取得

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

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

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

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

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

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

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

  1. php artisan passport:client --personal

パーソナルアクセスクライアントを作成した後、クライアントの ID とプレーンテキストのシークレット値をアプリケーションの .env ファイルに配置してください:

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

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

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

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

JSON API

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

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

GET /oauth/scopes

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

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

GET /oauth/personal-access-tokens

このルートは、認証されたユーザーが作成したすべてのパーソナルアクセストークンを返します。これは、ユーザーがトークンを編集または取り消すことができるようにするために主に役立ちます:

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

POST /oauth/personal-access-tokens

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

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

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

このルートは、パーソナルアクセストークンを取り消すために使用できます:

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

ルートの保護

ミドルウェアを介して

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

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

クライアント資格情報グラント を使用している場合は、client ミドルウェアを使用してルートを保護するべきです。auth:api ミドルウェアの代わりに。

複数の認証ガード

アプリケーションが異なるタイプのユーザーを認証する場合、全く異なる Eloquent モデルを使用する可能性があります。そのため、アプリケーション内の各ユーザープロバイダータイプに対してガード設定を定義する必要があります。これにより、特定のユーザープロバイダー向けのリクエストを保護できます。たとえば、次のガード設定の config/auth.php 設定ファイルを考えてみましょう:

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

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

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

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

アクセストークンの渡し方

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

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

トークンスコープ

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

スコープの定義

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

  1. /**
  2. * Bootstrap any application services.
  3. */
  4. public 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 メソッドから呼び出すべきです:

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

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

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

認可コードを要求する際

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

  1. Route::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. return redirect('http://passport-app.test/oauth/authorize?'.$query);
  9. });

個人用アクセストークンを発行する際

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

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

スコープの確認

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

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

すべてのスコープを確認

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

  1. Route::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 つ を持っていることを確認するためにルートに割り当てることができます:

  1. Route::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 メソッドを使用してトークンが特定のスコープを持っているかどうかを確認できます:

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

追加のスコープメソッド

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

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

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

  1. Passport::scopes();

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

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

指定されたスコープが定義されているかどうかを hasScope メソッドを使用して確認できます:

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

JavaScript での API の消費

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

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

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

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

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

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

クッキー名のカスタマイズ

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

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

CSRF 保護

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

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

イベント

Passport は、アクセストークンとリフレッシュトークンを発行する際にイベントを発生させます。これらのイベントをリッスンすることができます これにより、データベース内の他のアクセストークンを削除または取り消すことができます:

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

テスト

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

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

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

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