Laravel Passportは、OAuth2.0仕様に完全準拠した認可サーバー(OAuth2 Server)をLaravelアプリケーションに素早く導入できる公式パッケージです。「トークン認証を実装したいけれど、Laravel Sanctumとどちらを選ぶべき?」「Grant(グラントタイプ)はどう使い分ける?」「最新のLaravel 11 / 12でPassportをセットアップする正しい手順は?」と疑問を持つ方も多いでしょう。本記事では、Laravel Passportの概要からSanctumとの明確な使い分け基準、主要なGrant種別の考え方、最新バージョンでのインストール・初期設定、スコープ管理、自動テスト、そして現場でよく起きるトラブルの対処法まで徹底解説します。
Laravel Passportとは?
Laravel Passportは、PHPの標準的なOAuth2サーバーライブラリ「League OAuth2 Server」をベースに構築されたLaravel公式の認証・認可パッケージです。アクセストークンの発行、更新、失効、スコープ(権限範囲)によるアクセス制御、認可コードグラントやクライアントクレデンシャルグラントなどの各種OAuth2フローを標準で提供します。
主に以下のような要件があるシステムで強力な効果を発揮します。
- サードパーティ連携(外部アプリへのAPI公開):自社APIを第三者の外部Webサービスや連携アプリに公開し、「ユーザーの許可を得てAPIを利用させる(認可フロー)」仕組みを作りたい場合
- 複数クライアント種別の管理:Webアプリ、ネイティブモバイルアプリ、M2M(マシン間通信)など、クライアントの種類に応じた認可フローとトークン有効期限を厳格に管理したい場合
- きめ細やかな権限管理(スコープ):アクセストークンごとに「読み取りのみ」「注文作成のみ」といった操作権限(Scopes)を付与し、APIエンドポイント側で厳密に制限したい場合
Laravel SanctumとPassportの違い・使い分け
LaravelでAPI認証を実装する際、最も多く比較されるのが軽量パッケージのLaravel Sanctumです。両者の特徴と使い分けの基準を整理しましょう。
| 比較項目 | Laravel Sanctum | Laravel Passport |
|---|---|---|
| プロトコル | シンプルなAPIトークン / Cookieセッション認証 | OAuth 2.0 / 2.1 完全準拠 |
| 主なユースケース | 自社製SPA(Vue/React)、ファーストパーティモバイルアプリ、社内向けAPI | サードパーティ公開API、大規模プラットフォーム連携、外部システム連携 |
| 認可フロー(Grant) | なし(直接トークン発行またはCookieログイン) | 認可コード(PKCE)、クライアントクレデンシャル、デバイス認証など多彩 |
| トークン形式 | プレーンテキスト(DB保存) | 暗号化JWT(JSON Web Token)形式 |
| 権限管理 | Abilities(アビリティ)による制御 | OAuth2 Scopes(スコープ)による標準制御 |
| 導入・学習コスト | 非常に低い(設定も数行で完了) | 中〜高(OAuth2のプロトコル知識が必要) |
どちらを選ぶべきかの判断基準
- Sanctumを選ぶべきケース:フロントエンドとバックエンドが同じチームで開発されており、自社のSPA(Next.js、Nuxt、Vue、React等)やスマートフォンアプリと通信するだけであれば、迷わずSanctumを選択してください。保守が簡単でパフォーマンスにも優れています。
- Passportを選ぶべきケース:「Googleでログイン」「GitHubで連携」のようなOAuth2認可画面を自社サービスとして提供したい場合や、外部開発者がAPIを利用するエコシステムを構築したい場合はPassportが必須です。
OAuth2のGrant(認可グラント種別)の考え方
Laravel Passportを使いこなす上で最も重要なのが「Grant(認可グラント種別)」の理解です。クライアントの種類や用途に応じて適切なグラントタイプを選択します。
| グラントタイプ | 対象・用途 | 特徴・安全基準 | Passport既定 |
|---|---|---|---|
| 認可コードグラント (with PKCE) |
第三者のWebアプリ、SPA、ネイティブアプリ | OAuth2の標準フロー。ユーザーがブラウザで認可画面に同意してトークンを取得。PKCE(Proof Key for Code Exchange)により公開クライアントでも安全。 | 有効 |
| クライアントクレデンシャルグラント | サーバー間通信、バッチ処理、CLIツール | ユーザー(人間)を介さないマシン間通信(M2M)。Client IDとClient Secretのみで直接アクセストークンを取得。 | 有効 |
| パーソナルアクセストークン | 開発者向けテスト、社内スクリプト、CLI | ログイン済みユーザーが管理画面から直接発行する長期トークン。OAuthの認可リダイレクト不要。 | 有効 |
| デバイス認可グラント | スマートTV、IoT機器、入力制限端末 | ブラウザを持たない端末でQRコードや短いコードを表示し、別端末のブラウザで認証・認可を完了させるフロー。 | 有効 |
| リフレッシュトークングラント | アクセストークン更新時 | 短命なアクセストークンが失効した際、再ログインなしで新しいトークンを発行するフロー。 | 有効 |
| パスワードグラント (レガシー) |
自社製モバイルアプリ(旧来の方式) | ユーザー名とパスワードをクライアントに直接渡してトークンを得る方式。OAuth 2.1では非推奨。 | 無効(要設定) |
| 暗黙的グラント(Implicit) (レガシー) |
旧仕様のブラウザSPA | URLフラグメントで直接トークンを渡す古い方式。セキュリティ脆弱性のため非推奨。 | 無効(要設定) |
動作要件
- PHP 8.2以上
- Laravel 11.x / 12.x / 13.x
- Composer 2.x
Laravel Passportのインストールと初期設定手順
最新のLaravel環境におけるPassportの導入手順をステップ順に解説します。
ステップ1:Composerパッケージの追加
ターミナルでComposerを実行し、laravel/passportをインストールします。
composer require laravel/passport
ステップ2:install:api コマンドの実行
Laravel 11以降では、install:apiコマンドに--passportオプションを指定して初期化します(install:apiコマンドの詳細解説)。
php artisan install:api --passport
このコマンドを実行すると、以下の処理が自動で行われます。
- OAuth2用テーブル(
oauth_clients,oauth_access_tokens,oauth_refresh_tokens,oauth_auth_codes,oauth_personal_access_clients)のマイグレーションが作成・実行される - 暗号化鍵ファイル(
storage/oauth-private.key,storage/oauth-public.key)が生成される - パーソナルアクセストークン用クライアントが自動作成される
routes/api.phpが作成され、APIルーティングが有効化される
実行完了時に「Please add the [Laravel\Passport\HasApiTokens] trait to your User model.」と案内が表示されます。
ステップ3:Userモデルの設定
app/Models/User.phpを開き、Laravel\Passport\HasApiTokensトレイトとLaravel\Passport\Contracts\OAuthenticatableインターフェイスを追加します。
注意点:Laravel SanctumのLaravel\Sanctum\HasApiTokensと名前が同じですが名前空間が異なります。必ずLaravel\Passportからインポートしてください。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
}
ステップ4:認証ガードの設定(config/auth.php)
config/auth.phpのguards設定を確認し、apiガードのドライバをpassportに設定します。
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
※apiガードが設定されていないと、保護ルートへアクセスした際にInvalidArgumentException: Auth guard [api] is not defined.が発生します。
ステップ5:トークン有効期限の設定(AppServiceProvider.php)
既定ではトークンの有効期限は1年間です。セキュリティ強化のため、app/Providers/AppServiceProvider.phpのbootメソッドで適切な有効期限を設定しましょう。
<?php
namespace App\Providers;
use Carbon\Carbon;
use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Passport;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
// アクセストークンの有効期限(例:15日)
Passport::tokensExpireIn(now()->addDays(15));
// リフレッシュトークンの有効期限(例:30日)
Passport::refreshTokensExpireIn(now()->addDays(30));
// パーソナルアクセストークンの有効期限(例:6ヶ月)
Passport::personalAccessTokensExpireIn(now()->addMonths(6));
}
}
※Laravel 10以前の記事で見られるAuthServiceProviderへの記述やPassport::routes()の呼び出しは、Laravel 11以降では不要(Passport::routes()は削除済み)です。
実践1:パーソナルアクセストークンによるAPI認証
パーソナルアクセストークンは、OAuth2の複雑な認可リダイレクトを介さずに、ログイン済みユーザーのトークンを直接発行・利用できる仕組みです。
トークンの発行コード例
ユーザーインスタンスのcreateTokenメソッドを呼び出すだけでトークンが発行されます。
use App\Models\User;
$user = User::find(1);
// トークン名とスコープ(省略時は全権限)を指定して発行
$tokenResult = $user->createToken('Developer-Access-Token', ['read-posts']);
$accessToken = $tokenResult->accessToken;
// クライアントへレスポンス
return response()->json([
'token_type' => 'Bearer',
'access_token' => $accessToken,
]);
APIエンドポイントの定義(routes/api.php)
ルートを保護するには、auth:apiミドルウェアを指定します。
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:api');
curlによる動作確認
発行したトークンをAuthorization: Bearer <トークン>ヘッダーに付与してリクエストを送信します。
curl -X GET http://localhost:8000/api/user \
-H "Accept: application/json" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs..."
成功時のレスポンス(200 OK):
{
"id": 1,
"name": "Yamada Taro",
"email": "taro@example.com",
"email_verified_at": "2026-08-01T10:00:00.000000Z",
"created_at": "2026-08-01T10:00:00.000000Z",
"updated_at": "2026-08-01T10:00:00.000000Z"
}
無効・未認証時のレスポンス(401 Unauthorized):
{
"message": "Unauthenticated."
}
実践2:マシン間通信(クライアントクレデンシャルグラント)
外部サーバーからの自動バッチ処理やM2M連携では、ユーザーのログインを伴わない「クライアントクレデンシャルグラント」を使用します。
1. クライアントの作成
Artisanコマンドでマシン間連携用のクライアント(Client IDとClient Secret)を発行します。
php artisan passport:client --client --name="Payment-Sync-Service"
INFO New client created successfully.
Client ID ..... 019f8299-1dbc-7353-8ec2-e5977f6734b8
Client Secret . 33p0l5Glve6bryjtF0JYRL17OtAkm8TQZTma7cIf
WARN The client secret will not be shown again, so don't lose it!
2. トークンエンドポイントへPOSTリクエスト
クライアント側から/oauth/tokenに対してgrant_type=client_credentialsでリクエストします。
curl -X POST http://localhost:8000/oauth/token \
-H "Accept: application/json" \
-d "grant_type=client_credentials" \
-d "client_id=019f8299-1dbc-7353-8ec2-e5977f6734b8" \
-d "client_secret=33p0l5Glve6bryjtF0JYRL17OtAkm8TQZTma7cIf" \
-d "scope="
返却されるレスポンス:
{
"token_type": "Bearer",
"expires_in": 1296000,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs..."
}
3. クライアントクレデンシャル専用ミドルウェアで保護
マシン間専用のエンドポイントには、clientミドルウェアを適用します(Laravel 11+ではbootstrap/app.phpでエイリアス設定が必要です)。
Route::get('/system/metrics', function () {
return response()->json(['status' => 'healthy', 'uptime' => 99.98]);
})->middleware('client');
実践3:スコープ(Scopes)によるアクセス権限の細分化
OAuth2の大きな強みが「スコープ」による権限分離です。「読み取り専用トークン」「注文作成可能トークン」などを厳密に区別できます。
1. スコープの定義(AppServiceProvider.php)
利用可能なスコープの一覧と説明を定義します。
use Laravel\Passport\Passport;
public function boot(): void
{
Passport::tokensCan([
'read-posts' => '記事一覧・詳細の閲覧',
'write-posts' => '記事の新規作成・更新',
'delete-posts'=> '記事の削除',
]);
}
2. ミドルウェアの登録(bootstrap/app.php)
スコープ検証用ミドルウェアのエイリアスをbootstrap/app.phpに登録します。
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
use Laravel\Passport\Http\Middleware\CheckClientCredentials;
use Laravel\Passport\Http\Middleware\CheckForAnyScope;
use Laravel\Passport\Http\Middleware\CheckScopes;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
api: __DIR__.'/../routes/api.php',
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'scopes' => CheckScopes::class, // 指定したすべてのスコープが必要
'scope' => CheckForAnyScope::class, // 指定したいずれか1つのスコープが必要
'client' => CheckClientCredentials::class, // クライアントクレデンシャル認証
]);
})
->create();
3. ルートへのスコープ適用とコントローラー判定
ルート定義での制御:
// read-posts と write-posts の「両方」が必要
Route::post('/posts', [PostController::class, 'store'])
->middleware(['auth:api', 'scopes:read-posts,write-posts']);
// read-posts または admin の「いずれか」があればOK
Route::get('/posts', [PostController::class, 'index'])
->middleware(['auth:api', 'scope:read-posts,admin']);
コントローラー内での判定(tokenCan):
use Illuminate\Http\Request;
public function destroy(Request $request, string $id)
{
if (! $request->user()->tokenCan('delete-posts')) {
return response()->json(['message' => 'この操作を行う権限(delete-postsスコープ)がありません。'], 403);
}
// 削除処理...
}
Laravel Passportの自動テスト(Featureテスト)
Passport認証を適用したAPIエンドポイントは、テスト時に毎回トークンを発行せずとも、モックヘルパーを使って簡単にFeatureテストを記述できます。
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Passport\Passport;
use Tests\TestCase;
class PostApiTest extends TestCase
{
use RefreshDatabase;
public function test_user_with_read_scope_can_view_user_info(): void
{
$user = User::factory()->create();
// 指定ユーザーとスコープで認証状態を偽装
Passport::actingAs($user, ['read-posts']);
$response = $this->getJson('/api/user');
$response->assertStatus(200)
->assertJsonPath('email', $user->email);
}
public function test_user_without_scope_is_forbidden(): void
{
$user = User::factory()->create();
// write-postsスコープを持たない状態でアクセス
Passport::actingAs($user, ['read-posts']);
$response = $this->postJson('/api/posts', [
'title' => '新規記事タイトル',
]);
$response->assertStatus(403);
}
}
よくあるエラーとトラブルシューティング
| エラー・症状 | 主な原因 | 対処法 |
|---|---|---|
Auth guard [api] is not defined. |
config/auth.phpにapiガードが未登録 |
guards.apiに'driver' => 'passport', 'provider' => 'users'を追加する |
Route [login] not defined.(401ではなくリダイレクト) |
リクエストヘッダーにAccept: application/jsonが付与されていない |
APIクライアント(curl、Postman、Axios等)側で必ずAccept: application/jsonヘッダーを送信する |
Call to undefined method Laravel\Passport\Passport::routes() |
旧バージョン(Laravel 10以前)の古いコードが残っている | Passport::routes()の呼び出しを削除する(最新版は自動ルーティング登録のため不要) |
Personal access client not found. |
パーソナルアクセストークン用クライアントがDBに未作成 | php artisan passport:client --personalを実行してクライアントを作成する |
Encryption keys not found / 鍵読み込みエラー |
storage/oauth-private.keyが存在しないか、権限がない |
php artisan passport:keysを実行して鍵を再生成し、storage/のファイル権限を確認する |
Invalid scope(s) provided |
Passport::tokensCan()で定義されていないスコープ名をトークン発行時に指定した |
AppServiceProviderでスコープ名を正しく定義しているか確認する |
まとめ
Laravel Passportは、OAuth2.0仕様に完全準拠した認可サーバーをLaravel上で構築できる最も信頼性の高い公式パッケージです。
- Sanctumとの使い分け:自社SPA・モバイル向けはシンプルなLaravel Sanctum、サードパーティ連携や外部公開API・M2M認証はLaravel Passportを選択
- 最新の導入:
php artisan install:api --passportで一括セットアップし、UserモデルにHasApiTokensとOAuthenticatableを追加、config/auth.phpにpassportドライバを設定 - Grantとスコープの活用:ユースケースに合わせて認可コード(PKCE)、クライアントクレデンシャル、パーソナルアクセストークンを使い分け、スコープでエンドポイントを保護
- テスト:
Passport::actingAs()を用いてモック認証付きのFeatureテストを簡潔に実装可能
API設計やエンドポイント構築全般については、Laravel API作成完全ガイドもあわせて参考にしてください。
よくある質問(FAQ)
Q1. Laravel SanctumとPassportは1つのアプリケーションで併用できますか?
A. はい、技術的には併用可能です。例えば自社SPA向けにはauth:sanctumガードを使い、外部向けサードパーティAPIにはauth:api(Passport)ガードを適用してルーティングを分けることができます。ただし、UserモデルにインポートするHasApiTokensトレイトが重複するため、エイリアス定義など名前空間の衝突に注意が必要です。
Q2. 本番環境(AWS / GCP / Dockerコンテナ等)で秘密鍵(oauth-private.key)はどう管理すべきですか?
A. 秘密鍵ファイル(oauth-private.key)はGit管理(コミット)せず、.gitignoreに含めるのが鉄則です。マルチコンテナ環境やサーバーレス環境では、環境変数 PASSPORT_PRIVATE_KEY および PASSPORT_PUBLIC_KEY に鍵の文字列を直接設定することで、ファイル配置なしでPassportを動作させることができます。
Q3. 発行済みアクセストークンの無効化(ログアウト)はどう行いますか?
A. 認証済みリクエストから現在のトークンを取得し、revoke()メソッドを呼び出します。
Route::post('/logout', function (Request $request) {
$request->user()->token()->revoke();
return response()->json(['message' => 'ログアウトしました(トークンを無効化しました)。']);
})->middleware('auth:api');

コメント