Laravel 9.x Laravel Passport

イントロダクション

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

Warning!! このドキュメントは、皆さんがOAuth2に慣れていることを前提にしています。OAuth2について知らなければ、この先を続けて読む前に、一般的な用語とOAuth2の機能について予習してください。

Passportか?Sanctumか??

始める前に、アプリケーションがLaravel Passport、もしくはLaravel Sanctumのどちらがより適しているかを検討することをお勧めします。アプリケーションが絶対にOAuth2をサポートする必要がある場合は、Laravel Passportを使用する必要があります。

しかし、シングルページアプリケーションやモバイルアプリケーションを認証したり、APIトークンを発行したりする場合は、Laravel Sanctumを使用する必要があります。Laravel SanctumはOAuth2をサポートしていません。ただし、はるかにシンプルなAPI認証開発エクスペリエンスを提供します。

インストール

Composerパッケージマネージャにより、Passportをインストールすることからはじめましょう。

composer require laravel/passport

Passportのサービスプロバイダは独自のデータベースマイグレーションディレクトリを登録しているため、パッケージのインストール後にデータベースをマイグレーションする必要があります。Passportのマイグレーションにより、アプリケーションがOAuth2クライアントとアクセストークンを保存するために必要なテーブルが作成されます。

php artisan migrate

次に、passport:install Artisanコマンドを実行する必要があります。このコマンドは、安全なアクセストークンを生成するために必要な暗号化キーを作成します。さらに、このコマンドは、アクセストークンの生成に使用される「個人アクセス」および「パスワード許可」クライアントを作成します。

php artisan passport:install

Note: 自動増分整数の代わりに、PassportのClientモデルの主キー値としてUUIDを使用したい場合は、uuidsオプションを使いPassportをインストールしてください。

passport:installコマンドを実行し終えたら、Laravel\Passport\HasApiTokensトレイトをApp\Models\Userモデルへ追加してください。このトレイトは認証済みユーザーのトークンとスコープを調べられるように、モデルへ数個のヘルパメソッドを提供します。すでに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

--uuidsオプションを指定してpassport:installコマンドを実行することもできます。このオプションは、PassportのClientモデルの主キー値として整数を自動増分する代わりにUUIDを使用することをPassportに指示します。--uuidsオプションを指定してpassport:installコマンドを実行すると、Passportのデフォルトのマイグレーションを無効にするための追加の手順が表示されます。

php artisan passport:install --uuids

Passportのデプロイ

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

php artisan passport:keys

必要に応じて、Passportのキーをロードするパスを定義できます。これを実現するには、Passport::loadKeysFromメソッドを使用できます。通常、このメソッドは、アプリケーションのApp\Providers\AuthServiceProviderクラスのbootメソッドから呼び出す必要があります。

/**
 * 全認証/認可サービスの登録
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    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年後に有効期限が切れる長期アクセストークンを発行します。より長い/短いトークン有効期間を設定したい場合は、tokensExpireInrefreshTokensExpireInpersonalAccessTokensExpireInメソッドを使用します。これらのメソッドは、アプリケーションのApp\Providers\AuthServiceProviderクラスのbootメソッドで呼び出す必要があります。

/**
 * 全認証/認可サービスの登録
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::tokensExpireIn(now()->addDays(15));
    Passport::refreshTokensExpireIn(now()->addDays(30));
    Passport::personalAccessTokensExpireIn(now()->addMonths(6));
}

Warning!! Passportのデータベーステーブルのexpires_atカラムは読み取り専用であり、表示のみを目的としています。トークンを発行するとき、Passportは署名および暗号化されたトークン内に有効期限情報を保存します。トークンを無効にする必要がある場合は、取り消す必要があります。

デフォルトモデルのオーバーライド

独自のモデルを定義し、対応するPassportモデルを拡張することにより、Passportにより内部的に使用されるモデルを自由に拡張できます。

use Laravel\Passport\Client as PassportClient;

class Client extends PassportClient
{
    // ...
}

モデルを定義した後、Laravel\Passport\Passportクラスを介してカスタムモデルを使用するようにPassportに指示します。通常、アプリケーションのApp\Providers\AuthServiceProviderクラスの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;

/**
 * 全認証/認可サービスの登録
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    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;

/**
 * 全アプリケーションサービスの登録
 *
 * @return void
 */
public function register()
{
    Passport::ignoreRoutes();
}

そして、Passport自身のルートファイルで定義しているルートをアプリケーションのroutes/web.phpファイルへコピーして、好みに合わせ変更してください。

Route::group([
    'as' => 'passport.',
    'prefix' => config('passport.path', 'oauth'),
    'namespace' => 'Laravel\Passport\Http\Controllers',
], function () {
    // Passportのルート…
});

アクセストークンの発行

認証コードを介して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エンドポイントをすべて説明します。エンドポイントへのHTTPリクエスト作成をデモンストレートするため利便性を考慮し、Axiosを使用していきましょう。

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

GET /oauth/clients

このルートは認証されたユーザーの全クライアントを返します。ユーザーのクライアントの全リストは、主にクライアントを編集、削除する場合に役立ちます。

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

POST /oauth/clients

このルートは新クライアントを作成するために使用します。これには2つのデータが必要です。クライアントの名前(name)と、リダイレクト(redirect)のURLです。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}

このルートはクライアントを更新するために使用します。それには2つのデータが必要です。クライアントのnameredirectのURLです。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とシークレットを使用し、あなたのアプリケーションへ許可コードとアクセストークンをリクエストするでしょう。まず、API利用側アプリケーションは以下のように、あなたのアプリケーションの/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値を指定しない場合、要求されたスコープに対する消費者側アプリケーションへのアクセスをそのユーザーへ以前に許可していない場合のみ、認可のためのプロンプトを表示します。

Note: /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
{
    /**
     * クライアントが認可プロンプトを飛ばすべきか判定
     *
     * @return bool
     */
    public function skipsAuthorization()
    {
        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
    );

    $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属性は、アクセストークンが無効になるまでの秒数を含んでいます。

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

JSON API

Passportには、承認済みアクセストークンを管理するためのJSON APIも含んでいます。これを独自のフロントエンドと組み合わせ、アクセストークンを管理するダッシュボードをユーザーへ提供できます。便宜上、AxiosをエンドポイントへのHTTPリクエストを生成するデモンストレーションのため使用しています。JSON APIはwebauthミドルウェアにより保護されているため、自身のアプリケーションからのみ呼び出しできます。

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

// アクセストークンの取り消し
$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クラスでジョブの実行スケジュールを設定して、スケジュールに従ってトークンを自動的に整理することもできます。

/**
 * アプリケーションのコマンドスケジュール定義
 *
 * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
 * @return void
 */
protected function schedule(Schedule $schedule)
{
    $schedule->command('passport:purge')->hourly();
}

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

"Proof Key for Code Exchange" (PKCE)を使用する認可コードグラントは、シングルページアプリケーションやネイティブアプリケーションが、APIへアクセスするための安全な認証方法です。このグラントはクライアントの秘密コードを十分な機密を保ち保存できないか、もしくは認可コード横取り攻撃の危険を軽減する必要がある場合に、必ず使用すべきです。アクセストークンのために認可コードを交換するときに、クライアントの秘密コードを「コードベリファイヤ(code verifier)」と「コードチャレンジ(code challenge)」のコンピネーションに置き換えます。

クライアント生成

アプリケーションが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();
});

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

Warning!! パスワードグラントトークンの使用は、現在推奨していません。代わりに、OAuth2サーバが現在推奨しているグラントタイプ を選択する必要があります。

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

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

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

php artisan passport:client --password

トークンのリクエスト

パスワードグラントクライアントを作成したら、ユーザーのメールアドレスとパスワードを指定し、/oauth/tokenルートへPOSTリクエストを発行することで、アクセストークンをリクエストできます。このルートは、Passportが登録しているため、自分で定義する必要がないことを覚えておきましょう。リクエストに成功すると、サーバからaccess_tokenrefresh_tokenのJSONレスポンスを受け取ります。

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' => 'taylor@laravel.com',
    'password' => 'my-password',
    'scope' => '',
]);

return $response->json();

Note: アクセストークンはデフォルトで、長期間有効であることを記憶しておきましょう。ただし、必要であれば自由に、アクセストークンの最長持続時間を設定できます。

全スコープの要求

パスワードグラント、またはクライアント認証情報グラントを使用時は、あなたのアプリケーションでサポートする全スコープを許可するトークンを発行したいと考えるかと思います。*スコープをリクエストすれば可能です。*スコープをリクエストすると、そのトークンインスタンスのcanメソッドは、いつもtrueを返します。このスコープはpasswordclient_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' => 'taylor@laravel.com',
    '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;

    /**
     * 指定されたユーザー名のユーザーインスタンスを見つける
     *
     * @param  string  $username
     * @return \App\Models\User
     */
    public function findForPassport($username)
    {
        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パスワードグラントのために、ユーザーのパスワードをバリデート
     *
     * @param  string  $password
     * @return bool
     */
    public function validateForPassportPasswordGrant($password)
    {
        return Hash::check($password, $this->password);
    }
}

暗黙のグラントトークン

Warning!! 暗黙的のグラント・トークンの使用は、現在推奨していません。代わりに、OAuth2サーバが現在推奨しているグラントタイプ を選択する必要があります。

暗黙的なグラントは、認証コードグラントに似ています。ただし、トークンは認証コードを交換せずにクライアントへ返します。このグラントは、クライアントの利用資格情報を安全に保存できないJavaScriptまたはモバイルアプリケーションで最も一般的に使用します。このグラントを有効にするには、アプリケーションのApp\Providers\AuthServiceProviderクラスのbootメソッドでenableImplicitGrantメソッドを呼び出します。

/**
 * 全認証/認可サービスの登録
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

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

Note: /oauth/authorizeルートは、すでにPassportが定義づけていることを覚えておいてください。このルートを自分で定義する必要はありません。

クライアント認証情報グラントトークン

クライアント認証情報グラントはマシンーマシン間の認証に最適です。たとえば、APIによりメンテナンスタスクを実行する、定期実行ジョブに使用できます。

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

php artisan passport:client --client

次に、このグラントタイプを使用するために、app/Http/Kernel.phpファイルの$routeMiddlewareへ、CheckClientCredentialsミドルウェアを追加する必要があります。

use Laravel\Passport\Http\Middleware\CheckClientCredentials;

protected $routeMiddleware = [
    '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をユーザーに経験してもらう事ができますし、全般的なアクセストークン発行するシンプルなアプローチとしても役立つでしょう。

Note: あなたのアプリケーションが主にPassportを使用して個人アクセストークンを発行している場合、APIアクセストークンを発行するための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);

// スコープ無しのトークンを作成する
$token = $user->createToken('Token Name')->accessToken;

// スコープ付きのトークンを作成する
$token = $user->createToken('My Token', ['place-orders'])->accessToken;

JSON API

Passportにはパーソナルアクセストークンを管理するためのJSON APIも含まれています。ユーザーにパーソナルアクセストークンを管理してもらうダッシュボードを提供するため、APIと皆さんのフロントエンドを結びつける必要があるでしょう。以降から、パーソナルアクセストークンを管理するためのAPIエンドポイントをすべて説明します。利便性を考慮し、エンドポイントへのHTTPリクエスト作成をデモンストレートするために、Axiosを使用していきましょう。

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

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)と、トークンに割り付けるスコープ(scope)の、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は、受信リクエストのアクセストークンを検証する認証グラントを用意しています。passportドライバを使用するようにapiガードを設定したら、有効なアクセストークンを必要とするルートでauth:apiミドルウェアを指定するだけで済みます。

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

Warning!! クライアント認証情報グラントを使用している場合、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');

Note: 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\AuthServiceProviderクラスのbootメソッドのPassport::tokensCanメソッドを使用して定義できます。tokensCanメソッドは、スコープ名とスコープの説明の配列を引数に取ります。スコープの説明は任意で、承認画面でユーザーに表示されます。

/**
 * 全認証/認可サービスの登録
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::tokensCan([
        'place-orders' => 'Place orders',
        'check-status' => 'Check order status',
    ]);
}

デフォルトスコープ

クライアントが特定のスコープをリクエストしない場合は、setDefaultScopeメソッドを使用して、デフォルトのスコープをトークンへアタッチするようにPassportサーバを設定できます。通常、このメソッドは、アプリケーションのApp\Providers\AuthServiceProviderクラスのbootメソッドから呼び出す必要があります。

use Laravel\Passport\Passport;

Passport::tokensCan([
    'place-orders' => 'Place orders',
    'check-status' => 'Check order status',
]);

Passport::setDefaultScope([
    'check-status',
    'place-orders',
]);

Note: 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ファイルの$routeMiddlewareプロパティへ、以下のミドルウェアを追加してください。

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

APIをJavaScriptで利用

API構築時にJavaScriptアプリケーションから、自分のAPIを利用できたらとても便利です。このAPI開発のアプローチにより、世界中で共有されるのと同一のAPIを自身のアプリケーションで使用できるようになります。自分のWebアプリケーションやモバイルアプリケーション、サードパーティアプリケーション、そしてさまざまなパッケージマネージャ上で公開するSDKにより、同じAPIが使用されます。

通常、皆さんのAPIをJavaScriptアプリケーションから使用しようとするなら、アプリケーションに対しアクセストークンを自分で送り、それを毎回リクエストするたび、一緒にアプリケーションへ渡す必要があります。しかし、Passportにはこれを皆さんに変わって処理するミドルウェアが用意してあります。必要なのはapp/Http/Kernel.phpファイル中の、webミドルウェアグループに対し、CreateFreshApiTokenミドルウェアを追加することだけです。

'web' => [
    // 他のミドルウェア…
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

Warning!! ミドルウェアの指定の中で、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\AuthServiceProviderクラスのbootメソッドから呼び出す必要があります。

/**
 * 全認証/認可サービスの登録
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::cookie('custom_name');
}

CSRF保護

この認証方法を使用する場合、リクエストのヘッダに有効なCSRFトークンを確実に含める必要があります。デフォルトのLaravel JavaScriptスカフォールドはAxiosインスタンスを含み、同一オリジンリクエスト上にX-XSRF-TOKENヘッダを送るために、暗号化されたXSRF-TOKENクッキーを自動的に使用します。

Note: 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メソッドの最初の引数はユーザーのインスタンスで、第2引数はユーザートークンに許可するスコープ配列を指定します。

use App\Models\User;
use Laravel\Passport\Passport;

public function test_servers_can_be_created()
{
    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;

public function test_orders_can_be_retrieved()
{
    Passport::actingAsClient(
        Client::factory()->create(),
        ['check-status']
    );

    $response = $this->get('/api/orders');

    $response->assertStatus(200);
}

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる