【Laravel】ミドルウェアの作成と使い方|認証・ログ・レート制限の実装パターン

「ログインチェックをコントローラの先頭に書いていたら、気づけば10本以上のコントローラに同じコードが散らばっていた」──実務でよく見る状況です。認証・ログ出力・レート制限は要件としては別々でも、HTTP の入口では必ず組み合わさって現れます。

ミドルウェアを使えば、こうした共通処理をリクエストの入口でまとめて宣言的に制御できます。コントローラ側はビジネスロジックだけに集中でき、「どのルートにどのポリシーが効いているか」もルート定義を見るだけで把握できるようになります。

細かな API は Laravel のメジャーバージョンで変わることがあります。手元の composer.jsonlaravel/framework に合わせ、Middleware の公式ドキュメントの該当バージョンを開いて照合すると再現しやすいです。

はじめに

「API にだけログインチェックを入れたい」「Web と API で共通のリクエスト ID を付与したい」「特定エンドポイントだけ短時間のアクセスを制限したい」といった要件は頻出です。これらを各メソッドの先頭に書くと、テストや順序変更のたびに散らばったコードを追うことになります。

ミドルウェアでは、handle メソッドでリクエスト前後の処理をまとめ、ルート単位・ミドルウェアグループ単位・グローバル単位で適用順を制御できます。認証やログ、レート制限は、この適用順と組み合わせることが多いです。

この記事では次の範囲を整理します。

  • ミドルウェアの作成と登録の基本(新旧 アプリケーション構成への一言)
  • ルート/グループ/グローバルへの適用イメージ
  • 認証・ログ・レート制限を題材にした実装パターンと注意点
  • よくある失敗例と次に試せること

背景:ミドルウェアの実行順とグループ

Laravel では、リクエストがルートに到達するまでと、その後のレスポンス生成までに複数のミドルウェアが積まれます。Middleware の説明にあるとおり、handle($request, $next)$next を呼ぶ前後で前処理・後処理を書けます。複数ミドルウェアがある場合の並び順は、適用方法(グローバル・グループ・ルート指定)と定義順に依存します。

適用のしかた用途のイメージ
グローバルアプリ全体に共通する処理(少数・慎重に)
web / api などのグループ既定セットをベースに、グループ単位で挙動を変える
ルート単位特定エンドポイントだけに認証や権限制御

アプリケーション構成的には、Laravel 11 以降を前提としたプロジェクトでは bootstrap/app.php でミドルウェアを登録する形がよく見られます。それより前の世代では app/Http/Kernel.php が中心的だったため、レガシー構成ではファイル場所が異なる点だけ留意すると読み替えがしやすいです。

実装と検証

ミドルウェアの生成と最小実装

Artisan でクラスを生成します。

php artisan make:middleware LogIncomingRequest

典型的な前処理だけを書く例です。

<?php

namespace App\\Http\\Middleware;

use Closure;
use Illuminate\\Http\\Request;
use Symfony\\Component\\HttpFoundation\\Response;

class LogIncomingRequest
{
    public function handle(Request $request, Closure $next): Response
    {
        logger()->info('incoming', [
            'method' => $request->method(),
            'path' => $request->path(),
        ]);

        return $next($request);
    }
}

レスポンスが返るまで計測したい場合は、$next($request) のあとでレスポンスを受け取り、終了処理を書けます。Middleware の終了処理として、terminate メソッドを持つミドルウェアや、handle 内でレスポンスに対して後処理する書き方がドキュメントで説明されています。

登録とグループへの追加(構成依存)

実際の登録コードはプロジェクトの Laravel 世代により異なります。Middleware の登録では、エイリアス付けやグループへの追加が説明されています。

新しい構成では bootstrap/app.phpwithMiddlewareMiddleware オブジェクトを更新します。エイリアスやグループへの追加方法は公式ガイドに沿って説明されています。

「認証済みユーザーだけに許可したい」という要件では、組み込みの auth ミドルウェアをルートに付けるのが一般的です。セッション認証と Sanctum トークン認証でガードが異なるため、auth:sanctum のようにガード名を明示するケースも多いです。

// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', [UserController::class, 'show']);
    Route::post('/posts', [PostController::class, 'store']);
});

Laravel 11 以降の新構成では、bootstrap/app.php でエイリアスやグループへの追加を行います。

// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'log.request' => \\App\\Http\\Middleware\\LogIncomingRequest::class,
    ]);
    $middleware->appendToGroup('api', [
        \\App\\Http\\Middleware\\LogIncomingRequest::class,
    ]);
})

Laravel 10 以前の構成では app/Http/Kernel.php$middlewareAliases(旧 $routeMiddleware)に追加します。

// app/Http/Kernel.php
protected $middlewareAliases = [
    // ...
    'log.request' => \\App\\Http\\Middleware\\LogIncomingRequest::class,
];

バージョンとセットアップ(例: Sanctum やセッション)により、クラス名やエイリアスは異なります。ドキュメントの該当節で名前を確認するのが確実です。

ログパターン

ログ用途では次を切り分けると運用しやすくなります。

前処理ではリクエスト ID の発行や開始時刻の記録、ヘッダの検査をまとめます。

後処理ではステータスコードや処理時間を記録します。

<?php

namespace App\\Http\\Middleware;

use Closure;
use Illuminate\\Http\\Request;
use Illuminate\\Support\\Str;
use Symfony\\Component\\HttpFoundation\\Response;

class LogIncomingRequest
{
    public function handle(Request $request, Closure $next): Response
    {
        // 前処理:リクエスト ID を生成してヘッダとログに付与
        $requestId = (string) Str::uuid();
        $request->headers->set('X-Request-Id', $requestId);
        $startTime = microtime(true);

        logger()->info('request.start', [
            'request_id' => $requestId,
            'method'     => $request->method(),
            'path'       => $request->path(),
        ]);

        $response = $next($request);

        // 後処理:ステータスコードと処理時間を記録
        logger()->info('request.end', [
            'request_id' => $requestId,
            'status'     => $response->getStatusCode(),
            'duration_ms' => round((microtime(true) - $startTime) * 1000),
        ]);

        return $response->withHeaders(['X-Request-Id' => $requestId]);
    }
}

個人情報や認証情報がログに落ちないよう、ログ出力する項目はホワイトリスト的に限定する運用が安全側です。$request->all() をそのままログに流すと、パスワードやトークンが平文で残るため注意が必要です。

レート制限

レート制限はアプリケーション設計と密接であり、ルーティングのレート制限の節RateLimiter の定義やミドルウェアとの組み合わせが説明されています。

RateLimiter::for でリミッターを定義し、ルートの throttle: ミドルウェアから参照します。キーの設計が重要で、認証済みユーザーにはユーザー ID、未認証には IP を使うのが典型的なパターンです。

// app/Providers/AppServiceProvider.php の boot() 内、
// または Laravel 10 以前なら RouteServiceProvider の boot() 内
use Illuminate\\Cache\\RateLimiting\\Limit;
use Illuminate\\Support\\Facades\\RateLimiter;

RateLimiter::for('api', function (Request $request) {
    return $request->user()
        ? Limit::perMinute(60)->by($request->user()->id)
        : Limit::perMinute(10)->by($request->ip());
});

定義したリミッターはルートの throttle ミドルウェアで指定します。

// routes/api.php
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::get('/posts', [PostController::class, 'index']);
});

上限を超えるとフレームワークが自動で 429 レスポンスを返します。Limit::perMinute(60)->by(...)->response(...) でレスポンスをカスタマイズすることも可能です。公開 API で同一クライアントからの短時間大量アクセスを抑えたいときは、キーの決め方(送信元 IP・アカウント ID・API トークンなど)と上限値をセットで決めるとよいです。

実装時の注意点

実行順については、認証より前の段階でログインユーザー名をログへ出そうとすると、値は期待どおりになりにくいです。依存関係を整理し、ドキュメントにある適用順のイメージに沿って並べます。

副作用の範囲については、グローバルに登録したミドルウェアは想定外のルートにも効きます。適用範囲は必要最小限に抑えるのが無難です。

レスポンス後の処理については、ストリーミングレスポンスや特定のドライバでは terminate が期待どおりに動かないことがあります。Middleware の終了処理の説明にも触れられているため、計測要件がある場合は対象レスポンス種別を確認します。

設定とコードの二重管理に注意します。レート制限のキーや閾値をコードと設定ファイルの両方に散らすと、環境差分の把握が難しくなります。チームでどちらを正とするか決めておくとよいです。

失敗例:ルートにだけ付ければよいと思っていたが、api グループと二重に適用されていた

レガシーなプロジェクトを Laravel 10 → 11 に上げたときに実際に踏んだ失敗です。Kernel.php から bootstrap/app.php へ移行する過程で、api グループのデフォルト構成が変わっていることに気づかず、ルート側にも auth:sanctum を手で付けていました。

結果として、認証チェックが二重に走っており、ログを見て初めて同じミドルウェアが2回記録されていることに気づきました。動作は一見正常に見えるため、ログかデバッグバーがないと発見しにくいのが厄介なところです。

原因は、api グループに既定で含まれるミドルウェアを確認していなかったことです。Laravel 11 以降はデフォルトの api グループの中身が変わっているため、移行時は必ず現在の構成を確認するのが確実です。

# ルートに実際に適用されているミドルウェアを確認する
php artisan route:list --path=api

ルート側では「グループにまだ含まれていない、追加で必要なものだけ」を付けるという整理にしておくと、この種の取り違えを防ぎやすくなります。

学び:ミドルウェアは HTTP の共通ポリシーをコードとして固定する場所

認証・ログ・レート制限は、要件としては別々でも、HTTP の入口では組み合わせて現れます。ミドルウェアにまとめることで、「どのルートがどのポリシーの組み合わせか」を宣言的に読めるようになり、レビューやオンボーディングにも効きます。

まとめ

  • ミドルウェアは handle を中心に、リクエスト前後の共通処理をまとめる仕組みである。
  • 適用はグローバル・グループ・ルートがあり、認証・ログ・レート制限は適用順とセットで設計する。
  • プロジェクトの Laravel 世代により登録場所が異なるため、composer.json と公式 Middleware 章を突き合わせる。

次に試せること

  1. make:middleware でログ用ミドルウェアを作成し、ルート単位でだけ動く状態からグループ適用へ広げてみる。
  2. Rate Limiting の節に沿って、RateLimiter::for でキー設計を試す。
  3. 認証が必要なルートだけに限定したうえで、認証の Protecting Routesの例と自分のルート定義を突き合わせる。

Source

LaravelLaravel

Posted by 千原 耕司