LaravelでOAuth2認証を導入・管理する:Passportでトークン発行から運用まで固める手順

LaravelでOAuth2認証を導入・管理する:Passportでトークン発行から運用まで固める手順

OAuth2は「クライアント(アプリ)に対して、アクセストークンを発行し、APIアクセスを制御する」ための標準仕様。LaravelでOAuth2を扱う場合は、OAuth2サーバ実装を提供するLaravel Passportを使う構成が定番になる。導入時は“動く”だけでなく、クライアント種別(SPA/モバイル/サーバ間)、トークン寿命、リフレッシュ、スコープ、失効、鍵管理、ログ/監視まで決めておくと事故が減る。

OAuth2導入前に決めること:Passportが必要な条件を切り分ける

OAuth2準拠が必須(外部クライアント連携、第三者アプリ、正式なクライアント管理、スコープ、リフレッシュトークン等)が要件にあるならPassportが候補になる。
逆に「自社SPAのログイン」「軽いAPIトークン」「Cookieベースで十分」ならOAuth2自体が過剰になりやすい。要件が“OAuth2準拠”なのか、“APIを保護したいだけ”なのかを先に確定させる。

全体構成:クライアント・認可サーバ・リソースサーバの役割を分離する

・クライアント:SPA/モバイル/別サーバなど(トークンを受け取ってAPIを呼ぶ側)
・認可サーバ:Laravel(Passport)がトークンを発行する
・リソースサーバ:Laravel API(同一アプリ内でも役割としては分かれる)
設計ミスが起きやすいのは「SPAとAPIが同居しているのに、別ドメインの前提でCORS/CSRFを組む」など、前提の混在。

インストール:Passportを導入してOAuth2用テーブルと鍵を作る

Passportは専用テーブル(clients / tokens / auth codesなど)と暗号鍵が必要。Laravelの提供コマンドで一式が整う。

# Passportの導入(Laravelのコマンドが用意するセットアップ手順)
php artisan install:api --passport

エラー発生条件(典型):
・DB接続や権限不足でマイグレーションが失敗し、oauth系テーブルが作られない
・デプロイ環境でストレージ書き込み不可→鍵生成に失敗(permission denied)
・設定キャッシュが古く、QUEUE/DB/APP_URLなどが反映されず挙動がズレる(config:cacheの影響)

Userモデルの設定:HasApiTokensを付けてトークン検証に対応させる

API認証でトークンを扱うため、User(認証対象)にトレイトを付ける。付け忘れると、認証処理やガード周りで詰まる。

// app/Models/User.php(例)
namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;

    // ...
}

エラー発生条件:
・HasApiTokens未設定で、認証ミドルウェアが期待通り動かない
・ユーザー識別子がemail以外なのにPassport側がemail前提で探し、ログイン系フローが失敗する(username運用など)

クライアント作成と管理:用途別にクライアントを分ける

OAuth2は「どのクライアントにトークンを発行したか」を管理する。用途で分けると運用が楽になる。
・自社モバイルアプリ用
・管理画面SPA用
・サーバ間連携(バッチ/別サービス)用
・外部連携用(第三者アプリ)
クライアントID/Secretは漏洩前提で扱い、ローテーションと失効手順を用意する。

// app/Models/User.php(例)
namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;

    // ...
}

エラー発生条件:
・Secretをフロント(SPA)へ埋め込む(漏洩の温床)
・同一用途でクライアントを乱立し、失効/追跡が困難になる

グラントタイプの選定:Authorization Codeを基本にし、妥協点を明確にする

運用上の目安:
・外部/第三者連携:Authorization Code
・サーバ間(機械同士):Client Credentials
・個人トークン(手動発行・管理者用途):Personal Access Tokens
パスワードグラント(ユーザーID/パスワードをクライアントが扱う方式)は、要件とセキュリティレビューの合意がない限り採用しない方が安全。採用するなら、用途を“自社の第一者クライアント限定”に強制する。

API保護:認証ミドルウェアでトークン必須ルートを分ける

「公開API」と「要ログインAPI」をルートで分けると事故が減る。

// routes/api.php(例)
use Illuminate\Support\Facades\Route;

Route::get('/health', fn () => ['ok' => true]);

Route::middleware('auth:api')->group(function () {
    Route::get('/me', fn () => request()->user());
    Route::post('/orders', [\App\Http\Controllers\Api\OrderController::class, 'store']);
});

エラー発生条件:
・authミドルウェアの指定が違う(ガード名/設定の不一致)→常に401
・フロントがAuthorizationヘッダを送っていない→常に401
・トークン期限切れを未処理→突然401が増える

トークン発行:トークンエンドポイントを叩く最小例(サンプル)

クライアントからは、アクセストークンを取得してAPIへ Authorization: Bearer を付けて呼ぶ流れになる。

# トークン取得(例:grant_typeは用途に合わせる)
curl -X POST https://example.com/oauth/token \
  -H "Accept: application/json" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "scope="

# トークンを使って保護APIを叩く
curl https://example.com/api/me \
  -H "Accept: application/json" \
  -H "Authorization: Bearer ACCESS_TOKEN"

エラー発生条件(頻出):
・invalid_client:client_id/secret不一致、環境変数の取り違え、secretの前後空白
・unsupported_grant_type:grant_typeの指定が間違い
・invalid_scope:要求スコープが未定義
・401:Bearer未送信、期限切れ、失効済み

スコープ設計:API権限を機能単位で固定する

・orders:read / orders:write のように“名詞:操作”で揃える
・管理者専用はadmin:allのように分ける
・スコープ乱立を防ぐため命名規則を固定する

use Laravel\Passport\Passport;

Passport::tokensCan([
    'orders:read'  => '注文の参照',
    'orders:write' => '注文の作成・更新',
    'admin:all'    => '管理操作',
]);

リフレッシュと有効期限:短命アクセストークン+更新戦略

・アクセストークンは短め
・リフレッシュで継続
・401発生時の再取得フローをクライアント側で実装
長寿命トークンは漏洩時の被害が大きい。

失効(リボーク)とログアウト設計

・ログアウト時に現在トークンを失効
・管理者が全トークン失効できる設計
・クライアントID単位で停止可能にする

// 現在のアクセストークンを失効
$user = request()->user();
$user->token()->revoke();

エラー発生条件:
・失効処理が無く、トークンが残り続ける
・ユーザー削除のみでトークン管理が曖昧

鍵管理と環境差異:既存トークンが突然無効になる原因

・鍵ファイルの再生成
・複数台構成で鍵が不一致
・ストレージ永続化されていない
鍵は本番で安定管理する。

まとめ:LaravelでOAuth2(Passport)を安定運用するためのチェックポイント

・OAuth2が本当に必要かを要件で確定する
・install:api –passportで初期セットアップを完了させる
・クライアントは用途別に整理し、Secret管理を徹底する
・スコープ命名規則を固定する
・失効・監視・鍵管理まで含めて設計する