はじめに
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 コマンドを使用してインストールできます:
php artisan install:api --passport
このコマンドは、OAuth2 クライアントとアクセストークンを保存するために必要なテーブルを作成するためのデータベースマイグレーションを公開して実行します。また、セキュアなアクセストークンを生成するために必要な暗号化キーも作成します。
さらに、このコマンドは、Passport Client
モデルの主キー値として UUID を使用するか、自動インクリメント整数を使用するかを尋ねます。
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
メソッドを使用します。通常、このメソッドはアプリケーションの boot
クラスの App\Providers\AppServiceProvider
メソッドから呼び出されるべきです:
/**
* 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 にアップグレードする際は、アップグレードガイド を注意深く確認することが重要です。
設定
クライアントシークレットのハッシュ化
クライアントのシークレットがデータベースに保存される際にハッシュ化されるようにしたい場合は、Passport::hashClientSecrets
メソッドを boot
メソッドの中で呼び出す必要があります:
use Laravel\Passport\Passport;
Passport::hashClientSecrets();
有効にすると、すべてのクライアントシークレットは作成直後にのみユーザーに表示されます。プレーンテキストのクライアントシークレット値はデータベースに保存されないため、失われた場合にシークレットの値を回復することはできません。
トークンの有効期限
デフォルトでは、Passport は 1 年後に期限切れになる長寿命のアクセストークンを発行します。より長い/短いトークンの有効期限を設定したい場合は、tokensExpireIn
、refreshTokensExpireIn
、および personalAccessTokensExpireIn
メソッドを使用できます。これらのメソッドは、アプリケーションの boot
クラスの App\Providers\AppServiceProvider
メソッドから呼び出されるべきです:
/**
* 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
を返す場合、クライアントは承認され、ユーザーは即座に redirect_uri
にリダイレクトされます。ただし、消費アプリケーションが認可のためにリダイレクトする際に prompt
パラメータを明示的に設定している場合は除きます:
<?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
パラメータを検証する必要があります。状態パラメータが一致する場合、消費者はアプリケーションに 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
属性には、アクセストークンが期限切れになるまでの秒数が含まれています。
トークンの取り消し
revokeAccessToken
メソッドを Laravel\Passport\TokenRepository
で使用してトークンを取り消すことができます。revokeRefreshTokensByAccessTokenId
メソッドを Laravel\Passport\RefreshTokenRepository
で使用してトークンのリフレッシュトークンを取り消すことができます。これらのクラスは、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 コマンドがこれを行うことができます:
# 取り消されたトークンと期限切れのトークンおよび認可コードを削除...
php artisan passport:purge
# 6 時間以上期限切れのトークンのみを削除...
php artisan passport:purge --hours=6
# 取り消されたトークンと認可コードのみを削除...
php artisan passport:purge --revoked
# 期限切れのトークンと認可コードのみを削除...
php artisan passport:purge --expired
アプリケーションの routes/console.php
ファイルに スケジュールされたジョブ を設定して、スケジュールに従ってトークンを自動的に削除することもできます:
use Illuminate\Support\Facades\Schedule;
Schedule::command('passport:purge')->hourly();
PKCE を使用した認可コードグラント
「コード交換のための証明書」(PKCE) を使用した認可コードグラントは、シングルページアプリケーションやネイティブアプリケーションが API にアクセスするための安全な方法です。このグラントは、クライアントシークレットが機密に保存されることを保証できない場合や、攻撃者によって認可コードが傍受される脅威を軽減するために使用するべきです。「コード検証子」と「コードチャレンジ」の組み合わせが、アクセストークンのために認可コードを交換する際にクライアントシークレットの代わりになります。
クライアントの作成
PKCE を使用した認可コードグラントを介してトークンを発行する前に、PKCE 対応のクライアントを作成する必要があります。これは、passport:client
Artisan コマンドを --public
オプションと共に使用して行うことができます:
php artisan passport:client --public
トークンの要求
コード検証子とコードチャレンジ
この認可グラントはクライアントシークレットを提供しないため、開発者はトークンを要求するためにコード検証子とコードチャレンジの組み合わせを生成する必要があります。
コード検証子は、43 文字から 128 文字の間のランダムな文字列で、文字、数字、"-"
、"."
、"_"
、"~"
文字を含む必要があります。これは RFC 7636 仕様 で定義されています。
コードチャレンジは、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
パラメータを検証する必要があります。標準の認可コードグラントと同様に、状態パラメータが一致する場合、消費者はアプリケーションに 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();
}
パスワードグラントクライアントの作成
アプリケーションがパスワードグラントを介してトークンを発行できるようにするには、パスワードグラントクライアントを作成する必要があります。これは、passport:client
Artisan コマンドを --password
オプションと共に使用して行うことができます。すでに passport:install
コマンドを実行している場合は、このコマンドを実行する必要はありません:
php artisan passport:client --password
トークンの要求
パスワードグラントクライアントを作成したら、ユーザーのメールアドレスとパスワードを使用して POST
リクエストを /oauth/token
ルートに発行することでアクセストークンを要求できます。このルートはすでに 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',
'username' => '',
'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',
'username' => '',
'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 を介してメンテナンスタスクを実行しているスケジュールされたジョブでこのグラントを使用することがあります。
アプリケーションがクライアント資格情報グラントを介してトークンを発行できるようにするには、--client
Artisan コマンドの passport: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 を使用してパーソナルアクセストークンを発行している場合は、API アクセストークンを発行するための Laravel の軽量ファーストパーティライブラリである Laravel Sanctum の使用を検討してください。
パーソナルアクセスクライアントの作成
アプリケーションがパーソナルアクセストークンを発行できるようにするには、パーソナルアクセスクライアントを作成する必要があります。これは、passport:client
Artisan コマンドを --personal
オプションと共に実行することで行うことができます。すでに 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"
パーソナルアクセストークンの管理
パーソナルアクセスクライアントを作成したら、createToken
メソッドを App\Models\User
モデルインスタンスで使用して、特定のユーザーのためにトークンを発行できます。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
が必要です:
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');
クライアント資格情報グラント を使用している場合は、client
ミドルウェアを使用してルートを保護するべきです。auth:api
ミドルウェアの代わりに。
複数の認証ガード
アプリケーションが異なるタイプのユーザーを認証する場合、全く異なる Eloquent モデルを使用する可能性があります。そのため、アプリケーション内の各ユーザープロバイダータイプに対してガード設定を定義する必要があります。これにより、特定のユーザープロバイダー向けのリクエストを保護できます。たとえば、次のガード設定の config/auth.php
設定ファイルを考えてみましょう:
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
'api-customers' => [
'driver' => 'passport',
'provider' => 'customers',
],
次のルートは、api-customers
ガードを利用し、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 消費者が注文を出す能力を必要とするわけではありません。代わりに、消費者が注文の出荷状況にアクセスするための認可のみを要求できるようにすることができます。言い換えれば、スコープはアプリケーションのユーザーがサードパーティアプリケーションが自分の代わりに実行できるアクションを制限できるようにします。
スコープの定義
API のスコープは、アプリケーションの App\Providers\AppServiceProvider
クラスの boot
メソッド内で Passport::tokensCan
メソッドを使用して定義できます。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
メソッドを使用して個人用アクセストークンを発行する場合、希望するスコープの配列をメソッドの第二引数として渡すことができます:
$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);
});
クッキー名のカスタマイズ
必要に応じて、laravel_token
クッキーの名前を Passport::cookie
メソッドを使用してカスタマイズできます。通常、このメソッドはアプリケーションの App\Providers\AppServiceProvider
クラスの boot
メソッドから呼び出すべきです:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::cookie('custom_name');
}
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 番目はユーザーのトークンに付与されるべきスコープの配列です:
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);
}