- はじめに
- インストール
- 設定
- アクセストークンの発行
- PKCE を使った認可コードグラント
- パスワードグラントトークン
- インプリシットグラントトークン
- クライアントクレデンシャルグラントトークン
- パーソナルアクセストークン
- ルートの保護
- トークンスコープ
- JavaScript での API 利用
- イベント
- テスト
#はじめに
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 を使うべきです。Sanctum は OAuth2 をサポートしていませんが、よりシンプルな API 認証の開発体験を提供します。
#インストール
まずは Composer パッケージマネージャーを使って Passport をインストールしてください。
composer require laravel/passport
Passport の サービスプロバイダー は独自のデータベースマイグレーションディレクトリを登録するため、パッケージをインストールした後にデータベースマイグレーションを実行してください。Passport のマイグレーションは、OAuth2 クライアントとアクセストークンを保存するためのテーブルを作成します。
php artisan migrate
次に、passport:install Artisan コマンドを実行してください。このコマンドは安全なアクセストークンを生成するために必要な暗号化キーを作成します。また、アクセストークンを生成するために使われる「パーソナルアクセス」クライアントと「パスワードグラント」クライアントも作成します。
php artisan passport:install
Passport の Client モデルの主キーに自動増分整数ではなく UUID を使いたい場合は、uuids オプションを使って Passport をインストールしてください。
passport:install コマンドを実行した後、App\Models\User モデルに Laravel\Passport\HasApiTokens トレイトを追加してください。このトレイトは認証済みユーザーのトークンやスコープを確認するためのヘルパーメソッドを提供します。すでに Laravel\Sanctum\HasApiTokens トレイトを使っている場合は、それを削除して構いません。
<?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',
],
],
#クライアント UUID
また、passport:install コマンドを --uuids オプション付きで実行することもできます。このオプションは、Passport に Client モデルの主キー値としてオートインクリメントの整数の代わりに UUID を使用するよう指示します。passport:install コマンドを --uuids オプション付きで実行すると、Passport のデフォルトのマイグレーションを無効化する方法に関する追加の指示が表示されます:
php artisan passport:install --uuids
#Passport のデプロイ
Passport を初めてアプリケーションのサーバーにデプロイする際は、passport:keys コマンドを実行する必要があります。このコマンドはアクセストークン生成に必要な暗号化キーを作成します。生成されたキーは通常ソース管理には含めません。
php artisan passport:keys
必要に応じて、Passport のキーを読み込むパスを指定できます。Passport::loadKeysFrom メソッドを使って設定します。通常はアプリケーションの App\Providers\AuthServiceProvider クラスの boot メソッド内で呼び出します。
/**
* 認証・認可サービスを登録します。
*/
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 のデフォルトマイグレーションを使わない場合は、App\Providers\AppServiceProvider クラスの register メソッド内で Passport::ignoreMigrations メソッドを呼び出してください。デフォルトマイグレーションは vendor:publish Artisan コマンドでエクスポートできます。
php artisan vendor:publish --tag=passport-migrations
#Passport のアップグレード
Passport の新しいメジャーバージョンにアップグレードする際は、アップグレードガイドをよく確認してください。
#設定
#クライアントシークレットのハッシュ化
クライアントのシークレットをデータベースに保存する際にハッシュ化したい場合は、App\Providers\AuthServiceProvider クラスの boot メソッド内で Passport::hashClientSecrets メソッドを呼び出してください。
use Laravel\Passport\Passport;
Passport::hashClientSecrets();
有効にすると、クライアントシークレットは作成直後にのみユーザーに表示されます。プレーンテキストのシークレットはデータベースに保存されないため、紛失した場合は復元できません。
#トークンの有効期限
デフォルトでは、Passport は1年の有効期限を持つ長期間有効なアクセストークンを発行します。より長いまたは短い有効期限に設定したい場合は、tokensExpireIn、refreshTokensExpireIn、personalAccessTokensExpireIn メソッドを使えます。これらはアプリケーションの App\Providers\AuthServiceProvider クラスの boot メソッド内で呼び出してください。
/**
* 認証・認可サービスを登録します。
*/
public function boot(): void
{
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
Passport::personalAccessTokensExpireIn(now()->addMonths(6));
}
Passport のデータベーステーブルの expires_at カラムは読み取り専用で表示用です。トークン発行時、期限情報は署名・暗号化されたトークン内に保存されます。トークンを無効化したい場合は、取り消しを行ってください。
#デフォルトモデルのオーバーライド
Passport が内部で使うモデルは自由に拡張できます。独自モデルを定義し、対応する Passport モデルを継承してください。
use Laravel\Passport\Client as PassportClient;
class Client extends PassportClient
{
// ...
}
モデルを定義したら、Laravel\Passport\Passport クラスを使って Passport にカスタムモデルを知らせます。通常はアプリケーションの App\Providers\AuthServiceProvider クラスの boot メソッド内で設定します。
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;
/**
* 認証・認可サービスを登録します。
*/
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 が定義するルートをカスタマイズしたい場合は、まず Passport が登録するルートを無視するように、アプリケーションの AppServiceProvider クラスの register メソッド内で Passport::ignoreRoutes を呼び出してください。
use Laravel\Passport\Passport;
/**
* アプリケーションサービスを登録します。
*/
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 のルート...
});
#アクセストークンの発行
認可コードを使った 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エンドポイントをすべて解説します。便宜上、HTTPリクエストの例にはAxiosを使用します。
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 => {
// レスポンスのエラーをリスト表示...
});
#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 => {
// レスポンスのエラーをリスト表示...
});
#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", または "login"
]);
return redirect('http://passport-app.test/oauth/authorize?'.$query);
});
promptパラメータはPassportアプリケーションの認証動作を指定するために使えます。
promptの値がnoneの場合、ユーザーがPassportアプリケーションに認証されていなければ常に認証エラーを返します。consentの場合は、すべてのスコープが既に許可されていても常に認可承認画面を表示します。loginの場合は、既にセッションがあっても常に再ログインを促します。
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
{
/**
* クライアントが認可プロンプトをスキップすべきか判定します。
*/
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,
'無効なstate値です。'
);
$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も提供しています。これを独自のフロントエンドと組み合わせて、ユーザーがアクセストークンを管理するダッシュボードを提供できます。便宜上、HTTPリクエストの例にはAxiosを使用します。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);
// アクセストークンを取り消す...
$tokenRepository->revokeAccessToken($tokenId);
// トークンに関連するすべてのリフレッシュトークンを取り消す...
$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
アプリケーションのApp\Console\Kernelクラスでスケジュールジョブを設定し、定期的にトークンを自動削除することもできます。
/**
* アプリケーションのコマンドスケジュールを定義します。
*/
protected function schedule(Schedule $schedule): void
{
$schedule->command('passport:purge')->hourly();
}
#PKCEを使った認可コードグラント
「Proof Key for Code Exchange」(PKCE)を使った認可コードグラントは、シングルページアプリケーションやネイティブアプリケーションがAPIに安全に認証する方法です。クライアントシークレットを安全に保管できない場合や、認可コードが攻撃者に傍受されるリスクを軽減するために使います。認可コードをアクセストークンに交換する際に、クライアントシークレットの代わりに「コードベリファイア」と「コードチャレンジ」の組み合わせを使います。
#クライアントの作成
PKCE対応の認可コードグラントでトークンを発行するには、PKCE対応クライアントを作成する必要があります。passport:client Artisanコマンドに--publicオプションを付けて実行することで作成できます。
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 Serverで現在推奨されているグラントタイプを選択してください。
OAuth2のパスワードグラントは、モバイルアプリなどのファーストパーティクライアントがメールアドレス/ユーザー名とパスワードを使ってアクセストークンを取得できる仕組みです。これにより、ユーザーがOAuth2の認可コードリダイレクトフローを経ることなく、安全にアクセストークンを発行できます。
#パスワードグラントクライアントの作成
パスワードグラントでトークンを発行するには、まずパスワードグラントクライアントを作成する必要があります。passport:client Artisanコマンドに--passwordオプションを付けて実行してください。すでに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',
'username' => '[email protected]',
'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' => '[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;
/**
* 指定されたユーザー名に対応するユーザーインスタンスを取得します。
*/
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;
/**
* Passportのパスワードグラント用にユーザーのパスワードを検証します。
*/
public function validateForPassportPasswordGrant(string $password): bool
{
return Hash::check($password, $this->password);
}
}
#インプリシットグラントトークン
インプリシットグラントトークンの使用は推奨していません。代わりに、OAuth2 Serverで現在推奨されているグラントタイプを選択してください。
インプリシットグラントは認可コードグラントに似ていますが、認可コードを交換せずにトークンをクライアントに返します。このグラントは、クライアントクレデンシャルを安全に保存できないJavaScriptやモバイルアプリでよく使われます。有効にするには、アプリケーションのApp\Providers\AuthServiceProviderクラスのbootメソッド内でenableImplicitGrantメソッドを呼び出します。
/**
* 認証/認可サービスを登録します。
*/
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
次に、このグラントタイプを使うには、アプリケーションのapp/Http/Kernel.phpファイルの$middlewareAliasesプロパティにCheckClientCredentialsミドルウェアを追加します。
use Laravel\Passport\Http\Middleware\CheckClientCredentials;
protected $middlewareAliases = [
'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を試すために便利だったり、アクセストークン発行のより簡単な方法として役立ちます。
アプリケーションで主にパーソナルアクセストークンを発行する場合は、Laravelの軽量な公式APIアクセストークン発行ライブラリである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"
#パーソナルアクセストークンの管理
パーソナルアクセスクライアントを作成したら、App\Models\UserモデルのインスタンスでcreateTokenメソッドを使って特定のユーザーにトークンを発行できます。createTokenメソッドは最初の引数にトークン名を受け取り、2番目の引数にオプションでスコープの配列を受け取ります。
use App\Models\User;
$user = User::find(1);
// スコープなしでトークンを作成...
$token = $user->createToken('Token Name')->accessToken;
// スコープ付きでトークンを作成...
$token = $user->createToken('My Token', ['place-orders'])->accessToken;
#JSON API
Passportにはパーソナルアクセストークンを管理するためのJSON APIも含まれています。これをフロントエンドと組み合わせて、ユーザーがパーソナルアクセストークンを管理するダッシュボードを提供できます。以下では、パーソナルアクセストークン管理用のAPIエンドポイントをすべて紹介します。HTTPリクエストの例にはAxiosを使用します。
この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 => {
// レスポンスのエラーを一覧表示...
});
#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クライアントがアカウントへのアクセス権限を要求する際に、特定の権限セットを指定できる仕組みです。例えば、ECアプリケーションではすべてのAPI利用者が注文を出せる必要はありません。代わりに注文の配送状況を確認する権限だけを要求できるようにできます。つまり、スコープはユーザーが第三者アプリケーションに許可する操作を制限するためのものです。
#スコープの定義
APIのスコープは、アプリケーションのApp\Providers\AuthServiceProviderクラスのbootメソッド内でPassport::tokensCanメソッドを使って定義できます。tokensCanはスコープ名と説明の配列を受け取ります。説明は自由に設定でき、認可承認画面に表示されます。
/**
* 認証/認可サービスを登録します。
*/
public function boot(): void
{
Passport::tokensCan([
'place-orders' => '注文を出す',
'check-status' => '注文状況を確認する',
]);
}
#デフォルトスコープ
クライアントが特定のスコープを要求しない場合、setDefaultScopeメソッドでデフォルトスコープをトークンに付与するようPassportサーバーを設定できます。通常はApp\Providers\AuthServiceProviderのbootメソッド内で呼び出します。
use Laravel\Passport\Passport;
Passport::tokensCan([
'place-orders' => '注文を出す',
'check-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つのミドルウェアがあります。まず、app/Http/Kernel.phpの$middlewareAliasesプロパティに以下を追加してください。
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
#すべてのスコープをチェック
scopesミドルウェアは、リクエストのアクセストークンが指定されたすべてのスコープを持っているか検証します。
Route::get('/orders', function () {
// アクセストークンは "check-status" と "place-orders" の両方のスコープを持っている...
})->middleware(['auth:api', 'scopes:check-status,place-orders']);
#いずれかのスコープをチェック
scopeミドルウェアは、リクエストのアクセストークンが指定されたスコープのうち少なくとも1つを持っているか検証します。
Route::get('/orders', function () {
// アクセストークンは "check-status" または "place-orders" のいずれかのスコープを持っている...
})->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はウェブアプリケーション、モバイルアプリ、サードパーティアプリ、各種パッケージマネージャーで公開するSDKからも利用可能です。
通常、JavaScriptアプリケーションからAPIを利用するには、アクセストークンを手動で渡し、各リクエストに含める必要があります。しかし、Passportにはこれを自動で処理するミドルウェアが含まれています。app/Http/Kernel.phpのwebミドルウェアグループにCreateFreshApiTokenミドルウェアを追加するだけです。
'web' => [
// その他のミドルウェア...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
CreateFreshApiTokenミドルウェアはミドルウェアスタックの最後に配置してください。
このミドルウェアはレスポンスにlaravel_tokenクッキーを付与します。このクッキーには暗号化されたJWTが含まれており、Passportはこれを使ってJavaScriptアプリケーションからのAPIリクエストを認証します。JWTの有効期限はsession.lifetime設定値と同じです。ブラウザはこのクッキーを自動的にすべてのリクエストに送信するため、アクセストークンを明示的に渡さなくてもAPIリクエストが可能になります。
axios.get('/api/user')
.then(response => {
console.log(response.data);
});
#クッキー名のカスタマイズ
必要に応じて、Passport::cookie メソッドを使って laravel_token クッキーの名前をカスタマイズできます。通常、このメソッドはアプリケーションの App\Providers\AuthServiceProvider クラスの boot メソッド内で呼び出します。
/**
* 認証/認可サービスを登録します。
*/
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 はアクセストークンやリフレッシュトークンを発行するときにイベントを発生させます。これらのイベントを使って、データベース内の他のアクセストークンを削除または取り消すことができます。必要に応じて、アプリケーションの App\Providers\EventServiceProvider クラスでこれらのイベントにリスナーを登録できます。
/**
* アプリケーションのイベントリスナーのマッピング。
*
* @var array
*/
protected $listen = [
'Laravel\Passport\Events\AccessTokenCreated' => [
'App\Listeners\RevokeOldTokens',
],
'Laravel\Passport\Events\RefreshTokenCreated' => [
'App\Listeners\PruneOldTokens',
],
];
#テスト
Passport の actingAs メソッドは、現在認証されているユーザーとそのスコープを指定するために使えます。actingAs メソッドの第一引数はユーザーインスタンス、第二引数はユーザーのトークンに付与するスコープの配列です。
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 メソッドの第一引数はクライアントインスタンス、第二引数はクライアントのトークンに付与するスコープの配列です。
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);
}