コンテンツへスキップ

Laravel Passport

はじめに

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

exclamation

このドキュメントでは、既に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年後まで有効な長期間有効なアクセストークンを発行します。より長い/短いトークンの有効期限を設定したい場合は、tokensExpireInrefreshTokensExpireInpersonalAccessTokensExpireInメソッドを使用できます。これらのメソッドは、アプリケーションの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));
}
exclamation

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で定義されたルートをカスタマイズしたい場合があります。これを実現するには、まず、アプリケーションのAppServiceProviderregisterメソッドに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

このルートは、新しいクライアントを作成するために使用されます。クライアントのnameredirect 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}

このルートは、クライアントを更新するために使用されます。クライアントのnameredirect 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値が提供されない場合、ユーザーは、消費アプリケーションへのアクセスが要求されたスコープに対して以前に承認されていない場合にのみ、承認を求められます。

lightbulb

/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メソッドを定義することで実現できます。skipsAuthorizationtrueを返す場合、消費アプリケーションが承認のためにリダイレクト時に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_tokenrefresh_tokenexpires_in属性を含むJSONレスポンスを返します。expires_in属性には、アクセストークンが期限切れになるまでの秒数が含まれています。

lightbulb

/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_tokenrefresh_tokenexpires_in属性を含むJSONレスポンスを返します。expires_in属性には、アクセストークンが期限切れになるまでの秒数が含まれています。

トークンの取り消し

Laravel\Passport\TokenRepositoryrevokeAccessTokenメソッドを使用してトークンを取り消すことができます。Laravel\Passport\RefreshTokenRepositoryrevokeRefreshTokensByAccessTokenIdメソッドを使用して、トークンのリフレッシュトークンを取り消すことができます。これらのクラスは、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();
});

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

exclamation

パスワード付与トークンの使用は推奨されなくなりました。代わりに、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_tokenrefresh_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',
'username' => '[email protected]',
'password' => 'my-password',
'scope' => '',
]);
 
return $response->json();
lightbulb

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

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

パスワード付与またはクライアントクレデンシャル付与を使用する場合、アプリケーションでサポートされているすべてのスコープに対してトークンを承認することを希望する場合があります。これは、*スコープを要求することで実行できます。*スコープを要求すると、トークンインスタンスの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',
'username' => '[email protected]',
'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);
}
}

暗黙的グラントトークン

exclamation

暗黙的付与トークンの使用は推奨されなくなりました。代わりに、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);
});
lightbulb

/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を試したり、一般的にアクセストークンを発行するより簡単なアプローチとして役立ちます。

lightbulb

アプリケーションが主にパーソナルアクセストークンの発行に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');
exclamation

クライアントクレデンシャル付与を使用している場合は、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');
lightbulb

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',
]);
lightbulb

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,
]);
})
exclamation

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ヘッダーを送信します。

lightbulb

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);
}