Laravel Passport
- はじめに
- インストール
- 設定
- アクセストークンの発行
- PKCEを使用した承認コードグラント
- パスワードグラントトークン
- 暗黙的グラントトークン
- クライアントクレデンシャルグラントトークン
- パーソナルアクセストークン
- ルートの保護
- トークンスコープ
- JavaScriptを使用したAPIの利用
- イベント
- テスト
はじめに
Laravel Passport は、数分でLaravelアプリケーションに完全なOAuth2サーバー実装を提供します。Passportは、Andy MillingtonとSimon Hampによって保守されているLeague OAuth2サーバーの上に構築されています。
このドキュメントでは、既にOAuth2に精通していることを前提としています。OAuth2について何も知らない場合は、先に進む前に、一般的な用語とOAuth2の機能を理解することを検討してください。
PassportとSanctum、どちらを選ぶべきか?
開始する前に、アプリケーションがLaravel Sanctumの方が適しているかどうかを判断することをお勧めします。アプリケーションでOAuth2を絶対にサポートする必要がある場合は、Laravel Passportを使用する必要があります。
ただし、シングルページアプリケーション、モバイルアプリケーションを認証しようとしている場合、またはAPIトークンを発行しようとしている場合は、Laravel Sanctumを使用する必要があります。Laravel SanctumはOAuth2をサポートしませんが、はるかにシンプルなAPI認証開発エクスペリエンスを提供します。
インストール
install:api
Artisanコマンドを使用してLaravel Passportをインストールできます。
php artisan install:api --passport
このコマンドは、アプリケーションに必要なOAuth2クライアントとアクセストークンを保存するためのテーブルを作成するために必要なデータベースマイグレーションを公開して実行します。また、安全なアクセストークンを生成するために必要な暗号化キーも作成します。
さらに、このコマンドは、自動増分整数ではなくUUIDをPassportのClient
モデルの主キー値として使用するかどうかも尋ねます。
install:api
コマンドを実行した後、Laravel\Passport\HasApiTokens
トレイトをApp\Models\User
モデルに追加します。このトレイトは、認証されたユーザーのトークンとスコープを検査できるいくつかのヘルパーメソッドをモデルに提供します。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Foundation\Auth\User as Authenticatable;use Illuminate\Notifications\Notifiable;use Laravel\Passport\HasApiTokens; class User extends Authenticatable{ use HasApiTokens, HasFactory, Notifiable;}
最後に、アプリケーションのconfig/auth.php
設定ファイルで、api
認証ガードを定義し、driver
オプションをpassport
に設定する必要があります。これにより、アプリケーションは、着信APIリクエストの認証時にPassportのTokenGuard
を使用するように指示されます。
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ],],
Passportのデプロイ
初めてアプリケーションのサーバーにPassportをデプロイする際には、passport:keys
コマンドを実行する必要がある可能性があります。このコマンドは、アクセストークンを生成するためにPassportが必要とする暗号化キーを生成します。生成されたキーは通常、ソース管理に保存されません。
php artisan passport:keys
必要に応じて、Passportのキーを読み込むパスを定義できます。これを実現するには、Passport::loadKeysFrom
メソッドを使用できます。通常、このメソッドは、アプリケーションのApp\Providers\AppServiceProvider
クラスのboot
メソッドから呼び出される必要があります。
/** * Bootstrap any application services. */public function boot(): void{ Passport::loadKeysFrom(__DIR__.'/../secrets/oauth');}
環境からのキーの読み込み
あるいは、vendor:publish
Artisanコマンドを使用してPassportの設定ファイルを公開することもできます。
php artisan vendor:publish --tag=passport-config
設定ファイルが公開された後、環境変数として定義することで、アプリケーションの暗号化キーを読み込むことができます。
PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----<private key here>-----END RSA PRIVATE KEY-----" PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----<public key here>-----END PUBLIC KEY-----"
Passportのアップグレード
Passportの新しいメジャーバージョンにアップグレードする際は、アップグレードガイドを注意深く確認することが重要です。
設定
クライアントシークレットのハッシング
クライアントのシークレットをデータベースに保存する際にハッシュしたい場合は、アプリケーションのApp\Providers\AppServiceProvider
クラスのboot
メソッドでPassport::hashClientSecrets
メソッドを呼び出す必要があります。
use Laravel\Passport\Passport; Passport::hashClientSecrets();
有効にすると、すべてのクライアントシークレットは、作成直後のみユーザーに表示されます。プレーンテキストのクライアントシークレット値はデータベースに決して保存されないため、シークレット値が失われた場合にシークレット値を回復することはできません。
トークンの有効期限
デフォルトでは、Passportは1年後まで有効な長期間有効なアクセストークンを発行します。より長い/短いトークンの有効期限を設定したい場合は、tokensExpireIn
、refreshTokensExpireIn
、personalAccessTokensExpireIn
メソッドを使用できます。これらのメソッドは、アプリケーションのApp\Providers\AppServiceProvider
クラスのboot
メソッドから呼び出す必要があります。
/** * Bootstrap any application services. */public function boot(): void{ Passport::tokensExpireIn(now()->addDays(15)); Passport::refreshTokensExpireIn(now()->addDays(30)); Passport::personalAccessTokensExpireIn(now()->addMonths(6));}
Passportのデータベーステーブルのexpires_at
列は読み取り専用であり、表示目的のみです。トークンを発行する際、Passportは有効期限情報を署名付き暗号化トークン内に保存します。トークンを無効にする必要がある場合は、取り消す必要があります。
デフォルトモデルのオーバーライド
独自のモデルを定義し、対応するPassportモデルを拡張することで、Passportで内部的に使用されるモデルを自由に拡張できます。
use Laravel\Passport\Client as PassportClient; class Client extends PassportClient{ // ...}
モデルを定義した後、Laravel\Passport\Passport
クラスを使用して、カスタムモデルを使用するようにPassportに指示できます。通常、アプリケーションのApp\Providers\AppServiceProvider
クラスのboot
メソッドで、カスタムモデルについてPassportに通知する必要があります。
use App\Models\Passport\AuthCode;use App\Models\Passport\Client;use App\Models\Passport\PersonalAccessClient;use App\Models\Passport\RefreshToken;use App\Models\Passport\Token; /** * Bootstrap any application services. */public function boot(): void{ Passport::useTokenModel(Token::class); Passport::useRefreshTokenModel(RefreshToken::class); Passport::useAuthCodeModel(AuthCode::class); Passport::useClientModel(Client::class); Passport::usePersonalAccessClientModel(PersonalAccessClient::class);}
ルートのオーバーライド
Passportで定義されたルートをカスタマイズしたい場合があります。これを実現するには、まず、アプリケーションのAppServiceProvider
のregister
メソッドにPassport::ignoreRoutes
を追加することで、Passportによって登録されたルートを無視する必要があります。
use Laravel\Passport\Passport; /** * Register any application services. */public function register(): void{ Passport::ignoreRoutes();}
次に、そのルートファイルでPassportによって定義されたルートをアプリケーションのroutes/web.php
ファイルにコピーし、必要に応じて変更します。
Route::group([ 'as' => 'passport.', 'prefix' => config('passport.path', 'oauth'), 'namespace' => '\Laravel\Passport\Http\Controllers',], function () { // Passport routes...});
アクセストークンの発行
承認コードを使用したOAuth2は、多くの開発者がOAuth2に精通している方法です。承認コードを使用する場合、クライアントアプリケーションはユーザーをサーバーにリダイレクトします。そこで、ユーザーはクライアントにアクセストークンを発行するリクエストを承認するか拒否します。
クライアントの管理
まず、アプリケーションが貴社のアプリケーションのAPIと連携する必要がある開発者は、"クライアント"を作成することで、アプリケーションを貴社のアプリケーションに登録する必要があります。通常、これは、アプリケーションの名前と、ユーザーが承認リクエストを承認した後に貴社のアプリケーションがリダイレクトできるURLを指定することからなります。
passport:client
コマンド
クライアントを作成する最も簡単な方法は、passport:client
Artisanコマンドを使用することです。このコマンドは、OAuth2機能のテストのために独自のクライアントを作成するために使用できます。client
コマンドを実行すると、Passportはクライアントに関する詳細情報を求めるプロンプトを表示し、クライアントIDとシークレットを提供します。
php artisan passport:client
リダイレクトURL
クライアントに対して複数のリダイレクトURLを許可する場合は、passport:client
コマンドでURLの入力を求められたときに、コンマ区切りのリストを使用して指定できます。コンマを含むURLは、URLエンコードする必要があります。
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
このルートは、認証済みユーザーのすべてのクライアントを返します。これは主に、ユーザーのすべてのクライアントを一覧表示して編集または削除するために役立ちます。
axios.get('/oauth/clients') .then(response => { console.log(response.data); });
POST /oauth/clients
このルートは、新しいクライアントを作成するために使用されます。クライアントのname
とredirect
URLの2つのデータが必要です。redirect
URLは、ユーザーが承認リクエストを承認または拒否した後にリダイレクトされる場所です。
クライアントが作成されると、クライアントIDとクライアントシークレットが発行されます。これらの値は、アプリケーションからアクセストークンを要求するときに使用されます。クライアント作成ルートは、新しいクライアントインスタンスを返します。
const data = { name: 'Client Name', redirect: 'http://example.com/callback'}; axios.post('/oauth/clients', data) .then(response => { console.log(response.data); }) .catch (response => { // List errors on response... });
PUT /oauth/clients/{client-id}
このルートは、クライアントを更新するために使用されます。クライアントのname
とredirect
URLの2つのデータが必要です。redirect
URLは、ユーザーが承認リクエストを承認または拒否した後にリダイレクトされる場所です。このルートは、更新されたクライアントインスタンスを返します。
const data = { name: 'New Client Name', redirect: 'http://example.com/callback'}; axios.put('/oauth/clients/' + clientId, data) .then(response => { console.log(response.data); }) .catch (response => { // List errors on response... });
DELETE /oauth/clients/{client-id}
このルートは、クライアントを削除するために使用されます。
axios.delete('/oauth/clients/' + clientId) .then(response => { // ... });
トークンのリクエスト
承認のためのリダイレクト
クライアントが作成されると、開発者はクライアントIDとシークレットを使用して、アプリケーションから承認コードとアクセストークンを要求できます。まず、消費アプリケーションは、次のようにアプリケーションの/oauth/authorize
ルートにリダイレクトリクエストを行う必要があります。
use Illuminate\Http\Request;use Illuminate\Support\Str; Route::get('/redirect', function (Request $request) { $request->session()->put('state', $state = Str::random(40)); $query = http_build_query([ 'client_id' => 'client-id', 'redirect_uri' => 'http://third-party-app.com/callback', 'response_type' => 'code', 'scope' => '', 'state' => $state, // 'prompt' => '', // "none", "consent", or "login" ]); return redirect('http://passport-app.test/oauth/authorize?'.$query);});
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
ディレクトリに配置されます。
php artisan vendor:publish --tag=passport-views
ファーストパーティークライアントを承認する場合など、承認プロンプトをスキップしたい場合があります。これは、Client
モデルを拡張してskipsAuthorization
メソッドを定義することで実現できます。skipsAuthorization
がtrue
を返す場合、消費アプリケーションが承認のためにリダイレクト時にprompt
パラメーターを明示的に設定していない限り、クライアントは承認され、ユーザーはすぐにredirect_uri
にリダイレクトされます。
<?php namespace App\Models\Passport; use Laravel\Passport\Client as BaseClient; class Client extends BaseClient{ /** * Determine if the client should skip the authorization prompt. */ public function skipsAuthorization(): bool { return $this->firstParty(); }}
承認コードをアクセストークンに変換する
ユーザーが承認リクエストを承認すると、消費アプリケーションにリダイレクトされます。コンシューマーはまず、リダイレクトの前に保存された値に対してstate
パラメーターを検証する必要があります。stateパラメーターが一致する場合は、コンシューマーはアプリケーションにPOSTリクエストを発行してアクセストークンを要求する必要があります。リクエストには、ユーザーが承認リクエストを承認したときにアプリケーションによって発行された承認コードを含める必要があります。
use Illuminate\Http\Request;use Illuminate\Support\Facades\Http; Route::get('/callback', function (Request $request) { $state = $request->session()->pull('state'); throw_unless( strlen($state) > 0 && $state === $request->state, InvalidArgumentException::class, 'Invalid state value.' ); $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 'grant_type' => 'authorization_code', 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'redirect_uri' => 'http://third-party-app.com/callback', 'code' => $request->code, ]); return $response->json();});
この/oauth/token
ルートは、access_token
、refresh_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
このルートは、認証済みユーザーが作成したすべての承認済みアクセストークンを返します。これは主に、ユーザーのすべてのトークンを一覧表示して取り消すために役立ちます。
axios.get('/oauth/tokens') .then(response => { console.log(response.data); });
DELETE /oauth/tokens/{token-id}
このルートは、承認済みアクセストークンとその関連するリフレッシュトークンを取り消すために使用できます。
axios.delete('/oauth/tokens/' + tokenId);
トークンの更新
アプリケーションが短期的なアクセストークンを発行する場合、ユーザーは、アクセストークンが発行されたときに提供されたリフレッシュトークンを使用して、アクセストークンを更新する必要があります。
use Illuminate\Support\Facades\Http; $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 'grant_type' => 'refresh_token', 'refresh_token' => 'the-refresh-token', 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'scope' => '',]); return $response->json();
この/oauth/token
ルートは、access_token
、refresh_token
、expires_in
属性を含むJSONレスポンスを返します。expires_in
属性には、アクセストークンが期限切れになるまでの秒数が含まれています。
トークンの取り消し
Laravel\Passport\TokenRepository
のrevokeAccessToken
メソッドを使用してトークンを取り消すことができます。Laravel\Passport\RefreshTokenRepository
のrevokeRefreshTokensByAccessTokenId
メソッドを使用して、トークンのリフレッシュトークンを取り消すことができます。これらのクラスは、Laravelのサービスコンテナを使用して解決できます。
use Laravel\Passport\TokenRepository;use Laravel\Passport\RefreshTokenRepository; $tokenRepository = app(TokenRepository::class);$refreshTokenRepository = app(RefreshTokenRepository::class); // Revoke an access token...$tokenRepository->revokeAccessToken($tokenId); // Revoke all of the token's refresh tokens...$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);
トークンのパージ
トークンが取り消されたり期限切れになった場合は、データベースから削除することをお勧めします。Passportに含まれるpassport:purge
Artisanコマンドを使用すると、これを行うことができます。
# Purge revoked and expired tokens and auth codes...php artisan passport:purge # Only purge tokens expired for more than 6 hours...php artisan passport:purge --hours=6 # Only purge revoked tokens and auth codes...php artisan passport:purge --revoked # Only purge expired tokens and auth codes...php artisan passport:purge --expired
アプリケーションのroutes/console.php
ファイルにスケジュールされたジョブを設定して、スケジュールに従ってトークンを自動的に削除することもできます。
use Illuminate\Support\Facades\Schedule; Schedule::command('passport:purge')->hourly();
PKCEを使用した承認コードグラント
"Proof Key for Code Exchange" (PKCE) を使用した承認コード付与は、シングルページアプリケーションまたはネイティブアプリケーションをAPIに安全に認証するための方法です。クライアントシークレットが機密に保存されない可能性がある場合、または攻撃者によって承認コードが傍受される脅威を軽減するために、この付与を使用する必要があります。「コード検証子」と「コードチャレンジ」の組み合わせが、アクセストークンと承認コードを交換する際にクライアントシークレットに置き換わります。
クライアントの作成
アプリケーションがPKCEを使用して承認コード付与を介してトークンを発行する前に、PKCE対応のクライアントを作成する必要があります。これには、--public
オプションを使用してpassport:client
Artisanコマンドを使用します。
php artisan passport:client --public
トークンのリクエスト
コード検証子とコードチャレンジ
この承認付与はクライアントシークレットを提供しないため、開発者はトークンを要求するために、コード検証子とコードチャレンジの組み合わせを生成する必要があります。
コード検証子は、RFC 7636仕様で定義されているように、43〜128文字の英字、数字、"-"
、"."
、"_"
、"~"
文字を含むランダムな文字列である必要があります。
コードチャレンジは、URLおよびファイル名に安全な文字を含むBase64エンコードされた文字列である必要があります。末尾の'='
文字は削除する必要があり、改行、空白、その他の追加文字を含めることはできません。
$encoded = base64_encode(hash('sha256', $code_verifier, true)); $codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');
承認のためのリダイレクト
クライアントが作成された後、クライアントIDと生成されたコード検証子とコードチャレンジを使用して、アプリケーションから承認コードとアクセストークンを要求できます。まず、消費アプリケーションは、アプリケーションの/oauth/authorize
ルートにリダイレクトリクエストを行う必要があります。
use Illuminate\Http\Request;use Illuminate\Support\Str; Route::get('/redirect', function (Request $request) { $request->session()->put('state', $state = Str::random(40)); $request->session()->put( 'code_verifier', $code_verifier = Str::random(128) ); $codeChallenge = strtr(rtrim( base64_encode(hash('sha256', $code_verifier, true)) , '='), '+/', '-_'); $query = http_build_query([ 'client_id' => 'client-id', 'redirect_uri' => 'http://third-party-app.com/callback', 'response_type' => 'code', 'scope' => '', 'state' => $state, 'code_challenge' => $codeChallenge, 'code_challenge_method' => 'S256', // 'prompt' => '', // "none", "consent", or "login" ]); return redirect('http://passport-app.test/oauth/authorize?'.$query);});
承認コードをアクセストークンに変換する
ユーザーが承認リクエストを承認すると、消費アプリケーションにリダイレクトされます。コンシューマーは、標準的な承認コード付与と同様に、リダイレクトの前に保存された値に対してstate
パラメーターを検証する必要があります。
stateパラメーターが一致する場合は、コンシューマーはアプリケーションにPOSTリクエストを発行してアクセストークンを要求する必要があります。リクエストには、ユーザーが承認リクエストを承認したときにアプリケーションによって発行された承認コードと、最初に生成されたコード検証子を含める必要があります。
use Illuminate\Http\Request;use Illuminate\Support\Facades\Http; Route::get('/callback', function (Request $request) { $state = $request->session()->pull('state'); $codeVerifier = $request->session()->pull('code_verifier'); throw_unless( strlen($state) > 0 && $state === $request->state, InvalidArgumentException::class ); $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 'grant_type' => 'authorization_code', 'client_id' => 'client-id', 'redirect_uri' => 'http://third-party-app.com/callback', 'code_verifier' => $codeVerifier, 'code' => $request->code, ]); return $response->json();});
パスワードグラントトークン
パスワード付与トークンの使用は推奨されなくなりました。代わりに、OAuth2サーバーによって現在推奨されている付与タイプを選択する必要があります。
OAuth2パスワード付与を使用すると、モバイルアプリケーションなどの他のファーストパーティークライアントは、メールアドレス/ユーザー名とパスワードを使用してアクセストークンを取得できます。これにより、ユーザーがOAuth2承認コードのリダイレクトフロー全体を実行する必要なく、ファーストパーティークライアントに安全にアクセストークンを発行できます。
パスワード付与を有効にするには、アプリケーションのApp\Providers\AppServiceProvider
クラスのboot
メソッドでenablePasswordGrant
メソッドを呼び出します。
/** * Bootstrap any application services. */public function boot(): void{ Passport::enablePasswordGrant();}
パスワードグラントクライアントの作成
アプリケーションがパスワード付与を介してトークンを発行する前に、パスワード付与クライアントを作成する必要があります。これは、--password
オプションを指定してpassport:client
Artisanコマンドを使用することで実行できます。 **すでにpassport:install
コマンドを実行している場合は、このコマンドを実行する必要はありません。**
php artisan passport:client --password
トークンのリクエスト
パスワード付与クライアントを作成したら、ユーザーのメールアドレスとパスワードを使用して/oauth/token
ルートにPOST
リクエストを送信することで、アクセストークンを要求できます。このルートはPassportによって既に登録されているため、手動で定義する必要はありません。リクエストが成功すると、サーバーからJSONレスポンスでaccess_token
とrefresh_token
を受け取ります。
use Illuminate\Support\Facades\Http; $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 'grant_type' => 'password', 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'password' => 'my-password', 'scope' => '',]); return $response->json();
アクセストークンはデフォルトで長寿命です。ただし、必要に応じてアクセストークンの最大有効期間を設定できます。
すべてのスコープのリクエスト
パスワード付与またはクライアントクレデンシャル付与を使用する場合、アプリケーションでサポートされているすべてのスコープに対してトークンを承認することを希望する場合があります。これは、*
スコープを要求することで実行できます。*
スコープを要求すると、トークンインスタンスのcan
メソッドは常にtrue
を返します。このスコープは、password
またはclient_credentials
付与を使用して発行されたトークンにのみ割り当てることができます。
use Illuminate\Support\Facades\Http; $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 'grant_type' => 'password', 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'password' => 'my-password', 'scope' => '*',]);
ユーザープロバイダーのカスタマイズ
アプリケーションが複数の認証ユーザープロバイダーを使用している場合、artisan passport:client --password
コマンドを使用してクライアントを作成する際に--provider
オプションを提供することで、パスワード付与クライアントが使用するユーザープロバイダーを指定できます。指定されたプロバイダー名は、アプリケーションのconfig/auth.php
設定ファイルに定義されている有効なプロバイダーと一致する必要があります。その後、ミドルウェアを使用してルートを保護し、ガードで指定されたプロバイダーからのユーザーのみが承認されるようにすることができます。
ユーザー名フィールドのカスタマイズ
パスワード付与を使用して認証する場合、Passportは認証可能モデルのemail
属性を「ユーザー名」として使用します。ただし、モデルにfindForPassport
メソッドを定義することで、この動作をカスタマイズできます。
<?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable;use Illuminate\Notifications\Notifiable;use Laravel\Passport\HasApiTokens; class User extends Authenticatable{ use HasApiTokens, Notifiable; /** * Find the user instance for the given username. */ public function findForPassport(string $username): User { return $this->where('username', $username)->first(); }}
パスワードバリデーションのカスタマイズ
パスワード付与を使用して認証する場合、Passportはモデルのpassword
属性を使用して指定されたパスワードを検証します。モデルにpassword
属性がない場合、またはパスワード検証ロジックをカスタマイズする場合は、モデルにvalidateForPassportPasswordGrant
メソッドを定義できます。
<?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable;use Illuminate\Notifications\Notifiable;use Illuminate\Support\Facades\Hash;use Laravel\Passport\HasApiTokens; class User extends Authenticatable{ use HasApiTokens, Notifiable; /** * Validate the password of the user for the Passport password grant. */ public function validateForPassportPasswordGrant(string $password): bool { return Hash::check($password, $this->password); }}
暗黙的グラントトークン
暗黙的付与トークンの使用は推奨されなくなりました。代わりに、OAuth2サーバーによって現在推奨されている付与タイプを選択してください。
暗黙的付与は承認コード付与に似ていますが、承認コードを交換せずにトークンがクライアントに返されます。この付与は、クライアントクレデンシャルを安全に保存できないJavaScriptまたはモバイルアプリケーションで最も一般的に使用されます。付与を有効にするには、アプリケーションのApp\Providers\AppServiceProvider
クラスのboot
メソッドでenableImplicitGrant
メソッドを呼び出します。
/** * Bootstrap any application services. */public function boot(): void{ Passport::enableImplicitGrant();}
付与が有効になると、開発者はクライアントIDを使用してアプリケーションからアクセストークンを要求できます。消費アプリケーションは、次のようにアプリケーションの/oauth/authorize
ルートにリダイレクトリクエストを行う必要があります。
use Illuminate\Http\Request; Route::get('/redirect', function (Request $request) { $request->session()->put('state', $state = Str::random(40)); $query = http_build_query([ 'client_id' => 'client-id', 'redirect_uri' => 'http://third-party-app.com/callback', 'response_type' => 'token', 'scope' => '', 'state' => $state, // 'prompt' => '', // "none", "consent", or "login" ]); return redirect('http://passport-app.test/oauth/authorize?'.$query);});
/oauth/authorize
ルートはPassportによって既に定義されていることを覚えておいてください。このルートを手動で定義する必要はありません。
クライアントクレデンシャルグラントトークン
クライアントクレデンシャル付与は、マシン間認証に適しています。たとえば、メンテナンスタスクをAPIで実行するスケジュールされたジョブでこの付与を使用できます。
アプリケーションがクライアントクレデンシャル付与を介してトークンを発行する前に、クライアントクレデンシャル付与クライアントを作成する必要があります。これは、passport:client
Artisanコマンドの--client
オプションを使用して実行できます。
php artisan passport:client --client
次に、この付与タイプを使用するには、CheckClientCredentials
ミドルウェアのミドルウェアエイリアスを登録します。アプリケーションのbootstrap/app.php
ファイルでミドルウェアエイリアスを定義できます。
use Laravel\Passport\Http\Middleware\CheckClientCredentials; ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ 'client' => CheckClientCredentials::class ]);})
次に、ミドルウェアをルートにアタッチします。
Route::get('/orders', function (Request $request) { ...})->middleware('client');
ルートへのアクセスを特定のスコープに制限するには、ルートにclient
ミドルウェアをアタッチする際に、必要なスコープのコンマ区切りリストを提供できます。
Route::get('/orders', function (Request $request) { ...})->middleware('client:check-status,your-scope');
トークンの取得
この付与タイプを使用してトークンを取得するには、oauth/token
エンドポイントにリクエストを送信します。
use Illuminate\Support\Facades\Http; $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 'grant_type' => 'client_credentials', 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'scope' => 'your-scope',]); return $response->json()['access_token'];
パーソナルアクセストークン
ユーザーが、通常の承認コードリダイレクトフローを経ずに、自分自身にアクセストークンを発行したい場合があります。アプリケーションのUIを介してユーザーが自分自身にトークンを発行できるようにすると、ユーザーがAPIを試したり、一般的にアクセストークンを発行するより簡単なアプローチとして役立ちます。
アプリケーションが主にパーソナルアクセストークンの発行にPassportを使用している場合は、アクセストークンの発行のためのLaravelの軽量なファーストパーティライブラリであるLaravel Sanctumの使用を検討してください。
パーソナルアクセスクライアントの作成
アプリケーションがパーソナルアクセストークンを発行する前に、パーソナルアクセスクライアントを作成する必要があります。これは、--personal
オプションを指定してpassport:client
Artisanコマンドを実行することで実行できます。すでにpassport:install
コマンドを実行している場合は、このコマンドを実行する必要はありません。
php artisan passport:client --personal
パーソナルアクセスクライアントを作成したら、クライアントのIDとプレーンテキストのシークレット値をアプリケーションの.env
ファイルに入れます。
PASSPORT_PERSONAL_ACCESS_CLIENT_ID="client-id-value"PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET="unhashed-client-secret-value"
パーソナルアクセストークンの管理
パーソナルアクセスクライアントを作成したら、App\Models\User
モデルインスタンスのcreateToken
メソッドを使用して、特定のユーザーのトークンを発行できます。createToken
メソッドは、最初の引数としてトークンの名前、2番目の引数としてスコープのオプションの配列を受け取ります。
use App\Models\User; $user = User::find(1); // Creating a token without scopes...$token = $user->createToken('Token Name')->accessToken; // Creating a token with scopes...$token = $user->createToken('My Token', ['place-orders'])->accessToken;
JSON API
Passportには、パーソナルアクセストークンを管理するためのJSON APIも含まれています。これと独自のフロントエンドを組み合わせることで、ユーザーにパーソナルアクセストークンを管理するためのダッシュボードを提供できます。以下では、パーソナルアクセストークンを管理するためのすべてのAPIエンドポイントについて説明します。便宜上、Axiosを使用して、エンドポイントへのHTTPリクエストの実行を示します。
JSON APIはweb
およびauth
ミドルウェアによって保護されているため、独自のアプリケーションからのみ呼び出すことができます。外部ソースからは呼び出すことができません。
GET /oauth/scopes
このルートは、アプリケーションで定義されているすべてのスコープを返します。このルートを使用して、ユーザーがパーソナルアクセストークンに割り当てることができるスコープをリストできます。
axios.get('/oauth/scopes') .then(response => { console.log(response.data); });
GET /oauth/personal-access-tokens
このルートは、認証済みユーザーが作成したすべてのパーソナルアクセストークンを返します。これは主に、ユーザーのすべてのトークンをリストして編集または取り消すために役立ちます。
axios.get('/oauth/personal-access-tokens') .then(response => { console.log(response.data); });
POST /oauth/personal-access-tokens
このルートは新しいパーソナルアクセストークンを作成します。トークンのname
と、トークンに割り当てるscopes
の2つのデータが必要です。
const data = { name: 'Token Name', scopes: []}; axios.post('/oauth/personal-access-tokens', data) .then(response => { console.log(response.data.accessToken); }) .catch (response => { // List errors on response... });
DELETE /oauth/personal-access-tokens/{token-id}
このルートを使用して、パーソナルアクセストークンを取り消すことができます。
axios.delete('/oauth/personal-access-tokens/' + tokenId);
ルートの保護
ミドルウェアによる保護
Passportには、受信リクエストのアクセストークンを検証する認証ガードが含まれています。api
ガードでpassport
ドライバを使用するように設定したら、有効なアクセストークンを必要とするルートにauth:api
ミドルウェアを指定するだけです。
Route::get('/user', function () { // ...})->middleware('auth:api');
クライアントクレデンシャル付与を使用している場合は、auth:api
ミドルウェアの代わりにclient
ミドルウェアを使用してルートを保護する必要があります。
複数の認証ガード
アプリケーションが、完全に異なるEloquentモデルを使用する可能性のあるさまざまなタイプのユーザーを認証する場合、アプリケーションで各ユーザープロバイダータイプごとにガード設定を定義する必要がある可能性があります。これにより、特定のユーザープロバイダーを対象とするリクエストを保護できます。たとえば、次のガード設定がconfig/auth.php
設定ファイルにあるとします。
'api' => [ 'driver' => 'passport', 'provider' => 'users',], 'api-customers' => [ 'driver' => 'passport', 'provider' => 'customers',],
次のルートは、customers
ユーザープロバイダーを使用するapi-customers
ガードを使用して、受信リクエストを認証します。
Route::get('/customer', function () { // ...})->middleware('auth:api-customers');
Passportで複数のユーザープロバイダーを使用する方法の詳細については、パスワード付与ドキュメントを参照してください。
アクセストークンの渡し方
Passportによって保護されているルートを呼び出す場合、アプリケーションのAPIコンシューマーは、リクエストのAuthorization
ヘッダーにBearer
トークンとしてアクセストークンを指定する必要があります。たとえば、Guzzle HTTPライブラリを使用する場合
use Illuminate\Support\Facades\Http; $response = Http::withHeaders([ 'Accept' => 'application/json', 'Authorization' => 'Bearer '.$accessToken,])->get('https://passport-app.test/api/user'); return $response->json();
トークンスコープ
スコープを使用すると、APIクライアントは、アカウントへのアクセス権の承認を要求する際に、特定の権限セットを要求できます。たとえば、eコマースアプリケーションを構築している場合、すべてのAPIコンシューマーが注文を行う機能を必要とするわけではありません。代わりに、コンシューマーが注文の出荷状況へのアクセス権のみを要求できるようにできます。つまり、スコープを使用すると、アプリケーションのユーザーは、サードパーティアプリケーションが自分の代わりに実行できるアクションを制限できます。
スコープの定義
アプリケーションのApp\Providers\AppServiceProvider
クラスのboot
メソッドでPassport::tokensCan
メソッドを使用して、APIのスコープを定義できます。tokensCan
メソッドは、スコープ名とスコープの説明の配列を受け取ります。スコープの説明は自由に設定でき、承認承認画面にユーザーに表示されます。
/** * Bootstrap any application services. */public function boot(): void{ Passport::tokensCan([ 'place-orders' => 'Place orders', 'check-status' => 'Check order status', ]);}
デフォルトスコープ
クライアントが特定のスコープを要求しない場合、setDefaultScope
メソッドを使用して、Passportサーバーでトークンにデフォルトスコープをアタッチするように設定できます。通常、アプリケーションのApp\Providers\AppServiceProvider
クラスのboot
メソッドからこのメソッドを呼び出す必要があります。
use Laravel\Passport\Passport; Passport::tokensCan([ 'place-orders' => 'Place orders', 'check-status' => 'Check order status',]); Passport::setDefaultScope([ 'check-status', 'place-orders',]);
Passportのデフォルトスコープは、ユーザーによって生成されたパーソナルアクセストークンには適用されません。
トークンへのスコープの割り当て
承認コードを要求する場合
承認コード付与を使用してアクセストークンを要求する場合、コンシューマーは、scope
クエリ文字列パラメーターとして目的のスコープを指定する必要があります。scope
パラメーターは、スコープのスペース区切りリストである必要があります。
Route::get('/redirect', function () { $query = http_build_query([ 'client_id' => 'client-id', 'redirect_uri' => 'http://example.com/callback', 'response_type' => 'code', 'scope' => 'place-orders check-status', ]); return redirect('http://passport-app.test/oauth/authorize?'.$query);});
パーソナルアクセストークンを発行する場合
App\Models\User
モデルのcreateToken
メソッドを使用してパーソナルアクセストークンを発行する場合は、目的のスコープの配列をメソッドの2番目の引数として渡すことができます。
$token = $user->createToken('My Token', ['place-orders'])->accessToken;
スコープの確認
Passportには、受信リクエストが特定のスコープが付与されたトークンで認証されていることを検証するために使用できる2つのミドルウェアが含まれています。はじめに、アプリケーションのbootstrap/app.php
ファイルで次のミドルウェアエイリアスを定義します。
use Laravel\Passport\Http\Middleware\CheckForAnyScope;use Laravel\Passport\Http\Middleware\CheckScopes; ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ 'scopes' => CheckScopes::class, 'scope' => CheckForAnyScope::class, ]);})
すべてのスコープをチェックする
scopes
ミドルウェアは、受信リクエストのアクセストークンにリストされているすべてのスコープがあることを検証するために、ルートに割り当てることができます。
Route::get('/orders', function () { // Access token has both "check-status" and "place-orders" scopes...})->middleware(['auth:api', 'scopes:check-status,place-orders']);
任意のスコープをチェックする
scope
ミドルウェアは、受信リクエストのアクセストークンにリストされているスコープの少なくとも1つがあることを検証するために、ルートに割り当てることができます。
Route::get('/orders', function () { // Access token has either "check-status" or "place-orders" scope...})->middleware(['auth:api', 'scope:check-status,place-orders']);
トークンインスタンスでのスコープのチェック
アクセストークン認証済みリクエストがアプリケーションに入力された後も、認証済みApp\Models\User
インスタンスのtokenCan
メソッドを使用して、トークンに特定のスコープがあるかどうかを確認できます。
use Illuminate\Http\Request; Route::get('/orders', function (Request $request) { if ($request->user()->tokenCan('place-orders')) { // ... }});
追加のスコープメソッド
scopeIds
メソッドは、定義されているすべてのID/名前の配列を返します。
use Laravel\Passport\Passport; Passport::scopeIds();
scopes
メソッドは、Laravel\Passport\Scope
のインスタンスとして定義されているすべてのスコープの配列を返します。
Passport::scopes();
scopesFor
メソッドは、指定されたID/名と一致するLaravel\Passport\Scope
インスタンスの配列を返します。
Passport::scopesFor(['place-orders', 'check-status']);
hasScope
メソッドを使用して、特定のスコープが定義されているかどうかを確認できます。
Passport::hasScope('place-orders');
JavaScriptを使用したAPIの利用
APIを構築する際には、JavaScriptアプリケーションから独自のAPIを消費できることが非常に役立つ場合があります。このAPI開発のアプローチにより、独自のアプリケーションは、世界と共有しているのと同じAPIを消費できます。同じAPIは、Webアプリケーション、モバイルアプリケーション、サードパーティアプリケーション、およびさまざまなパッケージマネージャーで公開する可能性のあるSDKによって消費できます。
通常、JavaScriptアプリケーションからAPIを消費する場合、アクセストークンを手動でアプリケーションに送信し、各リクエストでアプリケーションに渡す必要があります。ただし、Passportにはこれを処理できるミドルウェアが含まれています。アプリケーションのbootstrap/app.php
ファイルのweb
ミドルウェアグループにCreateFreshApiToken
ミドルウェアを追加するだけです。
use Laravel\Passport\Http\Middleware\CreateFreshApiToken; ->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ CreateFreshApiToken::class, ]);})
CreateFreshApiToken
ミドルウェアは、ミドルウェアスタックにリストされている最後のミドルウェアであることを確認する必要があります。
このミドルウェアは、送信応答にlaravel_token
クッキーを付加します。このクッキーには、PassportがJavaScriptアプリケーションからのAPIリクエストの認証に使用する暗号化されたJWTが含まれています。JWTの有効期間は、session.lifetime
設定値と同じです。ブラウザは後続のすべてのリクエストでクッキーを自動的に送信するため、アクセストークンを明示的に渡すことなく、アプリケーションのAPIにリクエストを送信できます。
axios.get('/api/user') .then(response => { console.log(response.data); });
クッキー名のカスタマイズ
必要に応じて、Passport::cookie
メソッドを使用してlaravel_token
クッキーの名前をカスタマイズできます。通常、このメソッドはアプリケーションのApp\Providers\AppServiceProvider
クラスのboot
メソッドから呼び出す必要があります。
/** * Bootstrap any application services. */public function boot(): void{ Passport::cookie('custom_name');}
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番目の引数はユーザーのトークンに付与するスコープの配列です。
use App\Models\User;use Laravel\Passport\Passport; test('servers can be created', function () { Passport::actingAs( User::factory()->create(), ['create-servers'] ); $response = $this->post('/api/create-server'); $response->assertStatus(201);});
use App\Models\User;use Laravel\Passport\Passport; public function test_servers_can_be_created(): void{ Passport::actingAs( User::factory()->create(), ['create-servers'] ); $response = $this->post('/api/create-server'); $response->assertStatus(201);}
PassportのactingAsClient
メソッドを使用して、現在認証されているクライアントとそのスコープを指定できます。actingAsClient
メソッドに渡される最初の引数はクライアントインスタンス、2番目の引数はクライアントのトークンに付与するスコープの配列です。
use Laravel\Passport\Client;use Laravel\Passport\Passport; test('orders can be retrieved', function () { Passport::actingAsClient( Client::factory()->create(), ['check-status'] ); $response = $this->get('/api/orders'); $response->assertStatus(200);});
use Laravel\Passport\Client;use Laravel\Passport\Passport; public function test_orders_can_be_retrieved(): void{ Passport::actingAsClient( Client::factory()->create(), ['check-status'] ); $response = $this->get('/api/orders'); $response->assertStatus(200);}