Laravel Middleware徹底解説:認証、レート制限、カスタムミドルウェアの実装ガイド

Laravelミドルウェアの仕組みを基礎から応用まで解説します。認証ミドルウェア、throttleによるレート制限、カスタムミドルウェアの作成方法、そして本番環境で使える実践的なミドルウェアパターンを具体的なコード例とともに紹介します。

Laravelミドルウェアのアーキテクチャ:リクエストパイプライン、認証、レート制限の仕組み

ミドルウェアは、堅牢なLaravelアプリケーションの土台を支える重要な仕組みです。受信したHTTPリクエストとアプリケーションロジックの間に位置するフィルタ層として機能し、アクセス権限の検証、リクエスト頻度の制御、データの変換を行います。コントローラが1行のコードも実行する前に、これらの処理が完了します。認証、レート制限、独自の検証ロジックなど、Laravelを本番環境で運用するためにはミドルウェアパイプラインの仕組みを深く理解することが不可欠です。本記事では、具体的なコード例を用いて、ミドルウェアパイプラインの動作原理、Laravelが提供する組み込みミドルウェア、そして独自のミドルウェアを作成する方法を解説します。

ミドルウェアが不可欠な理由

ミドルウェアは、認証、ロギング、入力バリデーションといった横断的関心事をビジネスロジックから分離します。セキュリティチェックを各コントローラで繰り返す代わりに、ミドルウェアで一元的に定義し、関連するすべてのルートに自動的に適用できます。その結果、コントローラはスリムになり、セキュリティメカニズムの一貫性が保たれ、保守しやすいコードベースが実現します。

ミドルウェアパイプラインの仕組み

Laravelのミドルウェアパイプラインは、同心円状に重なるレイヤーとして理解すると分かりやすいです。各HTTPリクエストはこれらのレイヤーを外側から内側へと通過し、最終的にコントローラに到達します。レスポンスは同じレイヤーを逆方向に通過して返されます。この仕組みは、コントローラの処理前と処理後の両方でコードを実行するミドルウェアを見ると特に明確になります。

以下の例は、リクエストの処理時間を計測するミドルウェアです。$next($request)の呼び出し前のコードはリクエストがコントローラに到達する前に実行され、呼び出し後のコードはレスポンスが生成された後に実行されます。これはパイプラインのオニオンアーキテクチャの典型的な例です。

app/Http/Middleware/LogRequestTime.phpphp
namespace App\Http\Middleware;

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

class LogRequestTime
{
    public function handle(Request $request, Closure $next): Response
    {
        $start = microtime(true);          // Capture start time

        $response = $next($request);       // Pass to next middleware

        $duration = microtime(true) - $start;
        Log::info('Request completed', [
            'url'      => $request->url(),
            'method'   => $request->method(),
            'duration' => round($duration * 1000, 2) . 'ms',
        ]);

        return $response;                  // Return response up the stack
    }
}

ポイントは$next($request)にあります。この呼び出しによって、リクエストはチェーン内の次のミドルウェアに渡されます。この呼び出しより前のコードはBeforeミドルウェアロジック、後のコードはAfterミドルウェアロジックとなります。認証チェックの失敗などで$next()が一度も呼び出されない場合、リクエストはコントローラに到達しません。

認証ミドルウェア

認証は、Laravelにおけるミドルウェアの最も一般的なユースケースです。組み込みのauthミドルウェアは、ログイン済みのユーザーのみが保護されたルートにアクセスできることを保証します。未認証のユーザーはログインページに自動的にリダイレクトされるか、APIリクエストの場合は401レスポンスが返されます。

ルートは個別またはグループ単位で保護できます。グループ化はルート定義を見通しよく保ち、ルートが誤って保護されないまま残ることを防ぐため、推奨される方法です。

routes/web.phpphp
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\ProfileController;

// Single route protection
Route::get('/dashboard', [DashboardController::class, 'index'])
    ->middleware('auth');

// Group protection for multiple routes
Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'show']);
    Route::put('/profile', [ProfileController::class, 'update']);
    Route::delete('/profile', [ProfileController::class, 'destroy']);
});

マルチガード認証

Laravelは複数のAuthentication Guardを並行して使用することをサポートしています。Guardはユーザーの認証方法を定義します。セッション、APIトークン、カスタムメカニズムなど、様々な認証方式に対応できます。コロンの後にGuard名を指定することで、特定のルートグループに使用する認証メカニズムを制御できます。

routes/api.phpphp
// API routes use the 'sanctum' guard
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', fn (Request $request) => $request->user());
    Route::apiResource('/orders', OrderController::class);
});

// routes/web.php
// Admin routes use a custom 'admin' guard
Route::middleware('auth:admin')->prefix('admin')->group(function () {
    Route::get('/dashboard', [AdminController::class, 'index']);
    Route::get('/users', [AdminController::class, 'users']);
});

この分離により、APIルートをSanctumトークンで、管理者ルートを別のGuardで保護することが可能になります。各Guardは独自のユーザーモデル、プロバイダー、ドライバーを持つことができ、複雑なアプリケーションに対応する柔軟なアーキテクチャを構築できます。

guestミドルウェアについて

authミドルウェアの対になるものとしてguestミドルウェアが存在します。これは逆の動作を実装し、未認証のユーザーのみを通過させます。典型的なユースケースは、ログインページや登録ページにおいて、既にログイン済みのユーザーを自動的にダッシュボードへリダイレクトする場合です。使い方は同じで、Route::middleware('guest')->get('/login', ...)のように指定します。

throttleによるレート制限

レート制限は、アプリケーションを不正利用、ブルートフォース攻撃、意図しない過負荷から保護します。Laravelのthrottleミドルウェアは、クライアントが定義された時間枠内に送信できるリクエスト数を制限します。制限を超えた場合、LaravelはHTTPステータスコード429(Too Many Requests)を自動的に返します。

最もシンプルな設定方法は、ルート定義に直接記述することです。第1パラメータはリクエストの最大数、第2パラメータは時間枠(分単位)を指定します。

routes/api.phpphp
// Allow 60 requests per minute per user
Route::middleware('throttle:60,1')->group(function () {
    Route::get('/posts', [PostController::class, 'index']);
    Route::get('/posts/{post}', [PostController::class, 'show']);
});

// Stricter limit for write operations
Route::middleware(['auth:sanctum', 'throttle:10,1'])->group(function () {
    Route::post('/posts', [PostController::class, 'store']);
    Route::put('/posts/{post}', [PostController::class, 'update']);
});

この基本的な設定はシンプルなユースケースには十分ですが、ユーザーグループごとに異なる制限を設けるなど、より細かい要件に対しては、Laravelの名前付きRate Limiterが有効です。

名前付きRate Limiter

名前付きRate Limiterはサービスプロバイダで定義され、動的でコンテキストに応じた設定が可能です。以下の例は段階的なシステムを示しています。Enterpriseプランのユーザーは通常のユーザーよりも大きな制限が与えられ、匿名アクセスは最も厳しく制限されます。

app/Providers/AppServiceProvider.phpphp
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Http\Request;

public function boot(): void
{
    // API rate limiter with tiered access
    RateLimiter::for('api', function (Request $request) {
        $user = $request->user();

        if ($user?->hasSubscription('enterprise')) {
            return Limit::perMinute(500)->by($user->id);   // Enterprise: 500/min
        }

        if ($user) {
            return Limit::perMinute(100)->by($user->id);   // Authenticated: 100/min
        }

        return Limit::perMinute(20)->by($request->ip());   // Anonymous: 20/min
    });

    // Login limiter to prevent brute force
    RateLimiter::for('login', function (Request $request) {
        return Limit::perMinute(5)
            ->by($request->ip())                            // Key by IP address
            ->response(function () {                        // Custom exceeded response
                return response()->json([
                    'message' => 'Too many login attempts. Try again in a minute.',
                ], 429);
            });
    });
}

名前付きLimiterは、数値を直接指定する代わりに、ルート定義でその名前を参照して使用します。

routes/api.phpphp
Route::middleware('throttle:api')->group(function () {
    Route::apiResource('/posts', PostController::class);
});

// routes/web.php
Route::middleware('throttle:login')
    ->post('/login', [AuthController::class, 'login']);

このアプローチの利点は設定の一元管理にあります。制限値を変更すると、該当するLimiterを使用しているすべてのルートの動作が自動的に変わります。by()メソッドはリクエストのグループ化方法を制御し、ユーザーID、IPアドレス、またはそれらの組み合わせで分類できます。

Laravelの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

カスタムミドルウェアの作成

組み込みのミドルウェアクラスでは要件を満たせない場合、1つのArtisanコマンドで新しいミドルウェアクラスを生成できます。

bash
php artisan make:middleware EnsureUserHasRole

このコマンドにより、app/Http/Middleware/ディレクトリにhandleメソッドが準備されたクラスが作成されます。その後、必要なロジックを実装していきます。

ロールベースのアクセス制御

カスタムミドルウェアの典型的なユースケースとして、ロールベースの認可があります。以下のミドルウェアは、認証済みユーザーが必要なロールのいずれかを持っているかを検証します。スプレッド演算子(...$roles)により、可変個数のロールをパラメータとして受け取ることができます。

app/Http/Middleware/EnsureUserHasRole.phpphp
namespace App\Http\Middleware;

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

class EnsureUserHasRole
{
    public function handle(Request $request, Closure $next, string ...$roles): Response
    {
        $user = $request->user();

        if (! $user || ! $user->hasAnyRole($roles)) {
            abort(403, 'Insufficient permissions.');
        }

        return $next($request);
    }
}

bootstrap/app.phpでエイリアスとして登録することで、ルート定義において短く分かりやすい名前でミドルウェアを使用できるようになります。パラメータはコロンの後に指定し、複数の場合はカンマで区切ります。

bootstrap/app.phpphp
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'role' => \App\Http\Middleware\EnsureUserHasRole::class,
    ]);
})

// routes/web.php
Route::middleware('role:admin')->group(function () {
    Route::get('/admin', [AdminController::class, 'index']);
});

// Multiple roles: admin OR editor can access
Route::middleware('role:admin,editor')->group(function () {
    Route::resource('/articles', ArticleController::class);
});

APIリクエストのサニタイズ

もう一つの一般的なユースケースは、受信したAPIリクエストのバリデーションとサニタイズです。以下のミドルウェアは、POSTリクエストが正しいContent-Typeを使用していることを確認し、すべての文字列入力から不要な空白を除去します。

app/Http/Middleware/ApiRequestSanitizer.phpphp
namespace App\Http\Middleware;

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

class ApiRequestSanitizer
{
    public function handle(Request $request, Closure $next): Response
    {
        // Reject non-JSON requests on API routes
        if (! $request->expectsJson() && $request->isMethod('POST')) {
            return response()->json(
                ['error' => 'Content-Type must be application/json'],
                415
            );
        }

        // Trim all string inputs
        $input = $request->all();
        array_walk_recursive($input, function (&$value) {
            if (is_string($value)) {
                $value = trim($value);
            }
        });
        $request->merge($input);

        return $next($request);
    }
}

このミドルウェアはAPIミドルウェアグループの先頭に配置するのが理想的です。後続のすべてのミドルウェアクラスとコントローラが、既にサニタイズされたデータを受け取ることができます。

Laravel 12におけるミドルウェアの登録

Laravel 11以降、ミドルウェアの設定方法は大きく変わりました。以前のApp\Http\Kernelに代わり、すべての登録がbootstrap/app.phpで行われるようになっています。グローバルミドルウェア、グループミドルウェア、エイリアス、実行優先順位を一箇所で集中的に定義できます。

bootstrap/app.phpphp
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        // Global middleware (runs on every request)
        $middleware->append(
            \App\Http\Middleware\LogRequestTime::class
        );

        // Add to the 'web' middleware group
        $middleware->web(append: [
            \App\Http\Middleware\TrackPageViews::class,
        ]);

        // Add to the 'api' middleware group
        $middleware->api(prepend: [
            \App\Http\Middleware\ApiRequestSanitizer::class,
        ]);

        // Register aliases for route-level use
        $middleware->alias([
            'role'       => \App\Http\Middleware\EnsureUserHasRole::class,
            'subscribed' => \App\Http\Middleware\EnsureUserIsSubscribed::class,
        ]);

        // Control execution order
        $middleware->priority([
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\Auth\Middleware\Authenticate::class,
            \App\Http\Middleware\EnsureUserHasRole::class,
        ]);
    })
    ->create();

append()メソッドとprepend()メソッドは、ミドルウェアをグループの末尾に追加するか先頭に追加するかを制御します。グローバルミドルウェア(グループ指定なしの$middleware->append(...))は、WebルートかAPIルートかに関係なく、すべてのリクエストで実行されます。

実行順序に関する注意

ミドルウェアの実行順序は極めて重要です。認証を検証する前にセッションが開始されている必要があります。認可の前に認証が完了していなければなりません。レート制限は、負荷の高いビジネスロジックの前に適用されるべきです。priority()メソッドは、ルート上でミドルウェアがどの順序で定義されていても、この実行順序をグローバルに強制します。

Terminableミドルウェア

すべての処理がレスポンスをクライアントに送信する前に完了する必要はありません。Terminableミドルウェアは、レスポンスが完全に送信された後に実行されるterminate()メソッドを追加で実装します。クライアントはより速くレスポンスを受け取り、アプリケーションはバックグラウンドで追加の処理を行います。

app/Http/Middleware/CollectAnalytics.phpphp
namespace App\Http\Middleware;

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

class CollectAnalytics
{
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);  // Pass through without delay
    }

    public function terminate(Request $request, Response $response): void
    {
        // Runs after response is sent to client
        DB::table('analytics')->insert([
            'path'        => $request->path(),
            'method'      => $request->method(),
            'status_code' => $response->getStatusCode(),
            'user_id'     => $request->user()?->id,
            'ip'          => $request->ip(),
            'created_at'  => now(),
        ]);
    }
}

Terminableミドルウェアの典型的なユースケースは、アナリティクスの収集、監査ログの記録、キャッシュのウォームアップ、通知の送信です。注意すべき点として、Laravelはミドルウェアがサービスコンテナにシングルトンとして登録されていない限り、terminate()メソッド用に新しいインスタンスを生成します。ステートフルな処理を行う場合は、この点を考慮する必要があります。

実践的なミドルウェアパターン

本番環境では、標準的なユースケースを超えた反復的なミドルウェアパターンが頻繁に使用されます。特に有用な2つの例として、メンテナンスモードのバイパスとセキュリティヘッダーの設定があります。

メンテナンスモードのバイパス

メンテナンスモード中でも、開発者や管理者は変更を確認するためにアプリケーションにアクセスできる必要があります。以下のミドルウェアは、特定のIPアドレスからのアクセスを許可し、それ以外の訪問者にはメンテナンスページを表示します。

app/Http/Middleware/MaintenanceBypass.phpphp
class MaintenanceBypass
{
    private array $allowedIps = ['192.168.1.0/24', '10.0.0.1'];

    public function handle(Request $request, Closure $next): Response
    {
        if (app()->isDownForMaintenance()) {
            foreach ($this->allowedIps as $ip) {
                if ($request->ip() === $ip) {
                    return $next($request);
                }
            }
        }

        return $next($request);
    }
}

セキュリティヘッダー

HTTPセキュリティヘッダーは、本番アプリケーションにとって不可欠な要素です。Webサーバーの設定で追加する代わりに、ミドルウェアで直接定義することで、バージョン管理が容易になり、使用するWebサーバーに依存せずにヘッダーが設定されることを保証できます。

app/Http/Middleware/SecurityHeaders.phpphp
class SecurityHeaders
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $response->headers->set(
            'Strict-Transport-Security',
            'max-age=31536000; includeSubDomains'
        );

        return $response;
    }
}

どちらのミドルウェアクラスも、すべてのリクエストに適用されるべきであるため、グローバルミドルウェアとして登録するのが最適です。セキュリティヘッダーミドルウェアは、コントローラの処理後にレスポンスを変更するAfterミドルウェアの一例です。

Laravelの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

まとめ

Laravelのミドルウェアは、単なるフィルタ層にとどまりません。リクエスト処理の根幹を担い、セキュリティやインフラに関する関心事をビジネスロジックから分離するクリーンなアーキテクチャを実現します。本記事の要点を整理します。

  • パイプラインアーキテクチャ:各リクエストはミドルウェアクラスのチェーンを通過します。$next($request)に対するコードの位置によって、コントローラの処理前に実行されるか、処理後に実行されるかが決まります。
  • 認証ミドルウェアauthミドルウェアは、未認証のアクセスからルートを保護します。マルチガード設定により、Web、API、管理者エリアごとに異なる認証メカニズムを使い分けることができます。
  • レート制限throttleミドルウェアと名前付きRate Limiterは、シンプルな制限から段階的なロールベースのシステムまで、柔軟な設定オプションを提供します。
  • カスタムミドルウェアphp artisan make:middlewareコマンドで、数秒で新しいミドルウェアクラスを作成できます。パラメータ、スプレッド演算子、エイリアスにより、再利用可能で設定可能なミドルウェアを実装できます。
  • bootstrap/app.phpでの登録:Laravel 11以降、グローバルミドルウェア、グループ、エイリアス、優先順位を含むすべてのミドルウェア設定が1つのファイルに集約されています。
  • Terminableミドルウェアterminate()メソッドにより、レスポンス送信後のノンブロッキングな後処理が可能になります。アナリティクスや監査ログの記録に最適です。
  • 実践的パターン:メンテナンスモードのバイパスやセキュリティヘッダーは、本番環境のLaravelアプリケーションに必須のミドルウェアの典型例です。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

タグ

#laravel
#middleware
#authentication
#rate-limiting
#php

共有

関連記事