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

Laravelミドルウェアを実践的な例で解説します。認証ガード、throttleによるレート制限、カスタムミドルウェアの作成、Laravel 13のPHP属性、本番環境向けの高度なパターンを紹介します。

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

ミドルウェアは、受信したHTTPリクエストとアプリケーションロジックの間に位置するフィルタ層として機能します。すべてのリクエストはコントローラに到達する前にミドルウェアクラスのパイプラインを通過し、すべてのレスポンスは同じパイプラインを逆方向に通過して返されます。この仕組みの理解は、安全で高性能なLaravelアプリケーションを構築する上で不可欠です。

ミドルウェアの概要

ミドルウェアはHTTPリクエストをルートに到達する前に傍受します。Laravelはbootstrap/app.phpでFluent APIを使用してすべてのミドルウェアを登録します。組み込みミドルウェアは認証、CSRF保護、セッション管理、レート制限を標準で処理します。

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

Laravel HTTPカーネルは、すべてのリクエストをミドルウェアのスタックを通じて処理します。各ミドルウェアはリクエストを受け取り、ロジックを実行し、$next($request)を通じて次の層にリクエストを渡すか、レスポンスを直接返すことでパイプラインを中断します。

このアーキテクチャはChain of Responsibilityパターンに従います。ミドルウェアはリクエストがコントローラに到達する前(例:認証チェック)、レスポンスが生成された後(例:ヘッダーの追加)、またはその両方で動作できます。

app/Http/Middleware/LogRequestTime.phpphp
namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesLog;
use SymfonyComponentHttpFoundationResponse;

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

このミドルウェアはリクエストをラップします。処理前に開始時刻を記録し、レスポンスが返された後に処理時間をログに記録します。このbefore/afterパターンはミドルウェアの動作の核心です。

認証ミドルウェア:ルートの保護

Laravelにはauthミドルウェアエイリアスが付属しており、IlluminateAuthMiddlewareAuthenticateにマッピングされています。ルートに適用すると、認証済みユーザーのみがアクセスできるようになります。未認証ユーザーは401レスポンス(API)を受け取るか、ログインページにリダイレクトされます(Web)。

routes/web.phpphp
use AppHttpControllersDashboardController;
use AppHttpControllersProfileController;

// 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']);
});

マルチガード認証

複数のユーザータイプ(管理パネル、顧客エリア、API)を持つアプリケーションは、ガードベースの認証の恩恵を受けます。authミドルウェアはガードパラメータを受け取り、使用する認証ドライバーを指定できます。

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

コロンの後のガードパラメータは、Laravelにどの認証設定に対して検証するかを指示します。これにより、アプリケーションの異なる部分で認証ロジックをクリーンに分離できます。

guestミドルウェア

guestミドルウェアはauthの逆で、未認証ユーザーのみを通過させます。ログインや登録ルートに適用すると、既に認証済みのユーザーがこれらのページにアクセスするのを防ぎます。

throttleミドルウェアによるレート制限

Laravelミドルウェアのレート制限は、組み込みのthrottleミドルウェアを使用してルートを不正利用から保護します。最もシンプルな形式は、最大リクエスト数と分単位の時間枠の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']);
});

名前付きRate Limiterによる高度な制御

AppServiceProviderで名前付きRate Limiterを定義すると、ユーザーコンテキストに基づいた細かい制御が可能になります。このアプローチはインラインのthrottleパラメータよりも柔軟です。追加オプションについては公式レート制限ドキュメントを参照してください。

app/Providers/AppServiceProvider.phpphp
use IlluminateCacheRateLimitingLimit;
use IlluminateSupportFacadesRateLimiter;
use IlluminateHttpRequest;

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をルートに適用するにはthrottle:name構文を使用します。

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

上記の段階的なRate Limiterは本番環境のパターンを示しています。エンタープライズユーザーはより高い制限を取得し、認証済みユーザーは中程度の制限を受け、匿名リクエストは厳しく制限されます。by()メソッドはRate Limitのキーを決定し、認証済みユーザーにはユーザーIDを、フォールバックとしてIPアドレスを使用します。

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

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

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

組み込みミドルウェアが対応しないシナリオには、カスタムミドルウェアを作成します。make:middleware Artisanコマンドは、正しい構造を持つ新しいクラスをスキャフォールドします。

bash
php artisan make:middleware EnsureUserHasRole

ロールベースのアクセス制御ミドルウェア

一般的なカスタムミドルウェアパターンは、ルートレベルでロールベースの認可を強制し、パラメータとしてロール名を受け取ります。

app/Http/Middleware/EnsureUserHasRole.phpphp
namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;

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

可変長引数...$rolesパラメータにより、カンマで区切られた複数のロールを渡すことができます。登録と使用方法は以下の通りです。

bootstrap/app.phpphp
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'role' => AppHttpMiddlewareEnsureUserHasRole::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);
});

リクエスト変換ミドルウェア

ミドルウェアはコントローラに到達する前にリクエストを変更できます。コンテントタイプヘッダーを強制し、文字列入力をトリミングするJSON APIミドルウェアの例です。

app/Http/Middleware/ApiRequestSanitizer.phpphp
namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;

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

このミドルウェアは2つの関心事を処理します。POSTリクエストのコンテントタイプを検証し、すべての文字列入力の空白をトリミングしてサニタイズします。

bootstrap/app.phpでのミドルウェア登録

Laravelはすべてのミドルウェア登録をbootstrap/app.phpに集約しています。これはLaravel 11以前に存在した古いapp/Http/Kernel.phpアプローチに代わるものです。同じFluent APIがLaravel 12と13でも機能します。

bootstrap/app.phpphp
use IlluminateFoundationApplication;
use IlluminateFoundationConfigurationMiddleware;

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

        // Add to the 'web' middleware group
        $middleware->web(append: [
            AppHttpMiddlewareTrackPageViews::class,
        ]);

        // Add to the 'api' middleware group
        $middleware->api(prepend: [
            AppHttpMiddlewareApiRequestSanitizer::class,
        ]);

        // Register aliases for route-level use
        $middleware->alias([
            'role'       => AppHttpMiddlewareEnsureUserHasRole::class,
            'subscribed' => AppHttpMiddlewareEnsureUserIsSubscribed::class,
        ]);

        // Control execution order
        $middleware->priority([
            IlluminateSessionMiddlewareStartSession::class,
            IlluminateAuthMiddlewareAuthenticate::class,
            AppHttpMiddlewareEnsureUserHasRole::class,
        ]);
    })
    ->create();

priority配列は、同じルートに複数のミドルウェアが割り当てられている場合に重要です。Laravelはこのリストに従ってソートし、セッションが認証の前に開始され、認証がロールチェックの前に完了することを保証します。

ミドルウェア実行順序

ミドルウェアは登録された順序で実行されます。ルートレベルのミドルウェアでは、priority配列がデフォルトの順序を上書きします。未認証リクエストでロールをチェックすることを避けるため、常に認証を認可ミドルウェアの前に配置してください。

Laravel 13でのPHP属性によるミドルウェア

Laravel 13では、コントローラクラスおよびメソッドに直接ミドルウェアを宣言するための#[Middleware] PHP属性が導入されました。このアプローチにより、ミドルウェア設定が保護するコードと同じ場所に配置され、認可ルールの読み取りと保守が容易になります。

app/Http/Controllers/CommentController.phpphp
namespace AppHttpControllers;

use AppModelsComment;
use AppModelsPost;
use IlluminateRoutingAttributesControllersAuthorize;
use IlluminateRoutingAttributesControllersMiddleware;

#[Middleware('auth')]
class CommentController
{
    #[Middleware('subscribed')]
    #[Authorize('create', [Comment::class, 'post'])]
    public function store(Post $post)
    {
        // Only authenticated, subscribed users who can create comments reach here
    }

    public function index(Post $post)
    {
        // Still requires auth (from class-level attribute)
        return $post->comments;
    }
}

クラスレベルの#[Middleware('auth')]はすべてのメソッドに適用されます。メソッドレベルの属性はその上に積み重なります。store()はauthとsubscribedの両方を必要とします。#[Authorize]属性はLaravelのポリシーシステムと統合し、メソッド実行前にパーミッションをチェックします。

この属性ベースのアプローチはオプションです。ルートファイルでのミドルウェアとbootstrap/app.phpでの登録は引き続き完全にサポートされています。明示的なルート定義を好むチームはFluent APIを使用し続けることができます。自己文書化するコントローラを望むチームは属性を採用できます。

レスポンス後タスクのためのTerminableミドルウェア

Terminableミドルウェアは、レスポンスがクライアントに送信された後にロジックを実行します。これは、ユーザーをブロックすべきでないロギング、アナリティクス、クリーンアップタスクに便利です。

app/Http/Middleware/CollectAnalytics.phpphp
namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesDB;
use SymfonyComponentHttpFoundationResponse;

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(),
        ]);
    }
}

terminateメソッドは元のリクエストと最終レスポンスの両方を受け取ります。同じインスタンスがhandle()とterminate()の両方を処理するように、AppServiceProviderでミドルウェアをシングルトンとして登録してください。

本番環境向け実践的ミドルウェアパターン

本番環境のLaravelアプリケーションでは、いくつかのミドルウェアパターンが一貫して見られます。

メンテナンスモードバイパスは、メンテナンス中に内部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);
    }
}

セキュリティヘッダーは、HSTS、コンテントセキュリティポリシー、その他のヘッダーをすべてのレスポンスに追加します。

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

これらのパターンは2つの主要なミドルウェア位置を示しています。リクエスト前(メンテナンスバイパスはIPをチェックし、潜在的にブロック)とレスポンス後(セキュリティヘッダーは送信されるレスポンスを変更)です。

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

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

ソース

面接準備のためのLaravelミドルウェアパターン

  • Laravelミドルウェアはパイプラインとして動作します。各クラスはリクエストを処理し、それに作用し、前方に渡すかレスポンスで中断します
  • authミドルウェアは、ガードベースの認証でルートを保護し、auth:guard構文で複数のユーザータイプをサポートします
  • throttleミドルウェアと名前付きRateLimiter::for()定義によるレート制限は、ユーザーコンテキストに基づいた段階的なアクセス制御を可能にします
  • カスタムミドルウェアは、ロールチェック、リクエストサニタイズ、セキュリティヘッダーなどの横断的関心事をコントローラを煩雑にすることなく処理します
  • すべてのミドルウェア登録はbootstrap/app.phpでFluent APIを使用して行われ、priorityが実行順序を制御します
  • Laravel 13の#[Middleware]属性により、コントローラに直接ミドルウェアを宣言でき、認可ルールをハンドラと同じ場所に配置できます
  • Terminableミドルウェアは、ユーザー向けのレイテンシに影響を与えることなく、レスポンス後のタスク(アナリティクス、ロギング)を実行します
  • :param構文によるミドルウェアパラメータは、ルート定義を表現力豊かに保ち、ミドルウェアクラスを異なるコンテキストで再利用可能にします

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

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

今日のチャレンジ

Laravel のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月21日 更新

タグ

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

共有

関連記事