LaravelでWebアプリケーションやWeb APIを開発していると、「特定IPアドレスからのアクセスのみ許可したい」「管理者画面へのアクセス時に追加の認証チェックを行いたい」「すべてのリクエストの処理時間を計測してログに残したい」といった共通の処理を挟み込みたい場面が頻繁に訪れます。
こうしたHTTPリクエストとレスポンスの前後で実行される共通フィルタリング処理を一元管理するのがミドルウェア(Middleware)です。
Laravel 11以降では従来の app/Http/Kernel.php が廃止され、ミドルウェアの登録・設定がすべて bootstrap/app.php に集約されるという大幅なアーキテクチャの刷新が行われました。
- ミドルウェアの基本概念とパイプライン処理の仕組み(前処理と後処理)
- Artisanコマンドによるカスタムミドルウェアの作成手順
- Laravel 11以降の
bootstrap/app.phpにおける登録方法(グローバル・エイリアス・グループ) - ミドルウェアへの引数(パラメータ)渡しと終了処理(terminate)の実装
- 実務で即戦力となる4つの実践サンプルコード(IP制限・ログ計測・APIキー認証・セキュリティヘッダー)
- Laravel 10以前からの移行ポイントとよくあるトラブルの解決策
Laravelのミドルウェアとは?仕組みと役割をわかりやすく解説
ミドルウェアは、クライアント(ブラウザや外部APIクライアント)から送られてきたHTTPリクエストがアプリケーション(コントローラーやルーティングクロージャ)に到達する前、あるいはコントローラーが生成したHTTPレスポンスがクライアントへ返却される間に挟み込まれる「関所(フィルター)」のような仕組みです。
[クライアント (ブラウザ/API)]
│ (1) HTTP Request
▼
┌─────────────────────────────────┐
│ ミドルウェア1 (前処理: 認証/IP) │
│ └─► ミドルウェア2 (前処理) │
│ └─► [ コントローラー ] │
│ ┌─► ミドルウェア2 (後処理) │
│ ミドルウェア1 (後処理: ヘッダー/ログ)│
└─────────────────────────────────┘
│ (2) HTTP Response
▼
[クライアント (ブラウザ/API)]
HTTPリクエストとレスポンスのパイプライン処理
Laravelのミドルウェアは「パイプラインパターン」と呼ばれる設計パターンで動作しています。
多重のレイヤー(層)のように配置された各ミドルウェアがリクエストを順次受け取り、条件を満たしていれば次のレイヤーへ渡し($next($request))、条件を満たさなければその場でリダイレクトやエラーレスポンス(abort(403) など)を返してリクエストを遮断(ガード)します。
これにより、コントローラー内に認証ロジックやIP制限ロジックを個別で重複して書く必要がなくなり、関心の分離(Separation of Concerns)が実現され、保守性が劇的に向上します。
前処理(Before)と後処理(After)の違い
ミドルウェアには大きく分けて「前処理ミドルウェア」と「後処理ミドルウェア」の2つの実装パターンがあります。
- 前処理ミドルウェア(Before Middleware):コントローラーが実行される前にリクエストの検証や加工を行います。認証チェックやIP制限、リクエストデータの正規化などが該当します。
- 後処理ミドルウェア(After Middleware):コントローラーがレスポンスを生成した後に、そのレスポンスを受け取って加工やヘッダーの追加、ログ記録を行います。
前処理のコード例
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;
class EnsureUserIsSubscribed
{
public function handle(Request $request, Closure $next): Response
{
// コントローラー実行前のチェック(前処理)
if (! $request->user()?->subscribed()) {
return redirect()->route('subscription.plans');
}
return $next($request);
}
}
後処理のコード例
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;
class AddCustomHeader
{
public function handle(Request $request, Closure $next): Response
{
// 先に $next($request) を呼び出し、コントローラーで生成されたレスポンスを取得
$response = $next($request);
// レスポンスに対する操作(後処理)
$response->headers->set('X-Application-Name', 'LaravelWren');
return $response;
}
}
カスタムミドルウェアの作成手順(make:middlewareコマンド)
ここからは、実際に自分だけのカスタムミドルウェアを新規作成する手順を見ていきましょう。
make:middlewareコマンドの実行と生成場所
LaravelにはArtisanコマンドが用意されており、コマンド1発でミドルウェアのひな形(スケルトン)を生成できます。
ターミナルで以下のコマンドを実行します。
php artisan make:middleware CheckClientIp
コマンドを実行すると、app/Http/Middleware/CheckClientIp.php が自動生成されます。
handleメソッドの基本構造と$nextクロージャの仕組み
生成されたクラスファイルを開くと、handle メソッドが定義されています。
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;
class CheckClientIp
{
/**
* Handle an incoming request.
*
* @param Closure(IlluminateHttpRequest): (SymfonyComponentHttpFoundationResponse) $next
*/
public function handle(Request $request, Closure $next): Response
{
// ここにカスタムロジックを実装
return $next($request);
}
}
$request:現在処理中のHTTPリクエストオブジェクト(IlluminateHttpRequest)です。ヘッダーやIPアドレス、入力パラメータ、認証ユーザー情報などにアクセスできます。$next:パイプライン内の「次のミドルウェア」または「最終的なコントローラー」を実行するためのクロージャ(コールバック関数)です。$next($request)の呼び出し:この行を実行することで、リクエストが後続の処理へと流れます。条件を満たさない場合は$next($request)を呼ばずに別のレスポンス(リダイレクトやJSONエラー、abort())を返すことで、リクエストの通過を遮断できます。
Laravel 11以降のミドルウェア登録方法(bootstrap/app.php)
自作したミドルウェアは、作成しただけでは動作しません。アプリケーションに登録して適用範囲(全体・特定のルート・特定のグループなど)を指定する必要があります。
Laravel 11以降では、すべてのミドルウェア設定を bootstrap/app.php の ->withMiddleware() メソッド内で行います。
グローバルミドルウェアの登録(append / prepend)
グローバルミドルウェアとは、アプリケーションへのすべてのHTTPリクエストに対して無条件で実行されるミドルウェアです。
bootstrap/app.php 内で $middleware->append() または $middleware->prepend() を使用して登録します。
<?php
use AppHttpMiddlewareCheckClientIp;
use AppHttpMiddlewareMeasureExecutionTime;
use IlluminateFoundationApplication;
use IlluminateFoundationConfigurationExceptions;
use IlluminateFoundationConfigurationMiddleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// グローバルミドルウェアの末尾に追加
$middleware->append(MeasureExecutionTime::class);
// グローバルミドルウェアの先頭に追加(最も早く実行させたい場合)
$middleware->prepend(CheckClientIp::class);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
ルートミドルウェア(エイリアス)の登録とルーティングでの適用
特定のルートやコントローラーにのみミドルウェアを適用したい場合は、エイリアス(別名)を定義して登録するのが最も便利で一般的です。
1. bootstrap/app.php でエイリアスを定義
$middleware->alias() メソッドに連想配列を渡して登録します。
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'ip.check' => AppHttpMiddlewareCheckClientIp::class,
'role.check' => AppHttpMiddlewareCheckUserRole::class,
'api.key' => AppHttpMiddlewareValidateApiKey::class,
]);
})
2. routes/web.php や routes/api.php で適用
ルーティング定義側で ->middleware('エイリアス名') をチェーンして指定します。
use AppHttpControllersAdminController;
use IlluminateSupportFacadesRoute;
// 単一ルートへの適用
Route::get('/admin/dashboard', [AdminController::class, 'index'])
->middleware('ip.check');
// 複数のミドルウェアを配列で指定
Route::get('/admin/settings', [AdminController::class, 'settings'])
->middleware(['auth', 'ip.check']);
// ルートグループ全体への適用
Route::middleware(['auth', 'ip.check'])->prefix('admin')->group(function () {
Route::get('/users', [AdminController::class, 'users']);
Route::get('/reports', [AdminController::class, 'reports']);
});
エイリアスを定義せず、直接ミドルウェアのクラス名を渡すこともできます。Route::get('/special', [SpecialController::class, 'index'])->middleware(AppHttpMiddlewareCheckClientIp::class);
Web / API グループへのミドルウェア追加と除外(remove)
Laravelには標準で web(セッションやCSRF保護が有効なWeb画面用)と api(ステートレスなAPI用)の2つのルートグループが用意されています。
これらのグループに対して、一括で自作ミドルウェアを追加したり、既存の標準ミドルウェアを除外したりできます。
->withMiddleware(function (Middleware $middleware) {
// web グループの末尾に自作ミドルウェアを追加
$middleware->web(append: [
AppHttpMiddlewareAddCustomHeader::class,
]);
// api グループの先頭に自作ミドルウェアを追加
$middleware->api(prepend: [
AppHttpMiddlewareValidateApiKey::class,
]);
// 特定のルートでCSRF検証のみを除外したい場合
$middleware->validateCsrfTokens(except: [
'stripe/*',
'webhook/payment',
]);
})
ミドルウェアの実行優先順位(priority)の設定
複数のミドルウェアが同一ルートに適用されている場合、意図した順番で実行されるように優先順位(Priority)を明示的に指定できます。
->withMiddleware(function (Middleware $middleware) {
$middleware->priority([
IlluminateCookieMiddlewareEncryptCookies::class,
IlluminateSessionMiddlewareStartSession::class,
IlluminateViewMiddlewareShareErrorsFromSession::class,
AppHttpMiddlewareCheckClientIp::class,
IlluminateRoutingMiddlewareSubstituteBindings::class,
IlluminateAuthMiddlewareAuthenticate::class,
]);
})
ミドルウェアの応用機能とパラメータ渡し
ミドルウェアは単にリクエストを素通りさせるだけでなく、引数を受け取って動的に挙動を変更したり、レスポンス送信後のバックグラウンド処理を実行したりする高度な機能が備わっています。
ミドルウェアに引数(パラメータ)を渡す方法
例えば「管理者(admin)権限が必要なルート」と「編集者(editor)権限が必要なルート」を単一のミドルウェアで共通化したい場合、ミドルウェアに引数を渡すことができます。
1. handleメソッドに引数を追加
$next 引数の後ろに追加の引数を定義します。
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;
class CheckUserRole
{
public function handle(Request $request, Closure $next, string $role): Response
{
$user = $request->user();
// ログインしていない、または指定されたロールを持っていない場合は403エラー
if (! $user || ! $user->hasRole($role)) {
abort(403, 'このページへのアクセス権限がありません。');
}
return $next($request);
}
}
2. ルーティングで引数を指定(コロン記法)
ルーティング側では、ミドルウェア名:引数 の形式で値を渡します。
// 'admin' を引数として渡す
Route::get('/admin', [AdminController::class, 'index'])
->middleware('role.check:admin');
// 'editor' を引数として渡す
Route::get('/articles/edit', [ArticleController::class, 'edit'])
->middleware('role.check:editor');
// 複数引数の場合はカンマ区切り (handle: $role, $permission)
Route::get('/secret', [SecretController::class, 'index'])
->middleware('role.check:admin,manage-users');
レスポンス送信後の終了処理(terminateメソッド)の実装
ミドルウェア内に terminate メソッドを定義すると、ブラウザへHTTPレスポンスが完全に送信された直後に後処理を実行できます(FastCGIの fastcgi_finish_request 等を利用した仕組み)。
ユーザーのブラウザ表示速度(体感速度)を低下させずに、重いログ書き込みや集計処理を行いたい場合に極めて有効です。
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesLog;
use SymfonyComponentHttpFoundationResponse;
class LogAccessAfterResponse
{
public function handle(Request $request, Closure $next): Response
{
// 通常のハンドリング
return $next($request);
}
/**
* レスポンスがブラウザに送信された後に実行される
*/
public function terminate(Request $request, Response $response): void
{
// レスポンス送信後の非同期的なログ記録
Log::channel('access')->info('Request completed', [
'url' => $request->fullUrl(),
'status' => $response->getStatusCode(),
'user_id' => $request->user()?->id,
'memory_usage' => memory_get_peak_usage(true),
]);
}
}
実務でそのまま使えるカスタムミドルウェアの具体例4選
実務開発で頻繁に求められる要件をベースにした、コピペしてそのまま使える高品質なサンプルコードを4つ紹介します。
1. 特定IPアドレス制限ミドルウェア(アクセス元制御)
社内管理画面やステージング環境など、特定の許可されたIPアドレスからのみアクセスを許可し、それ以外は 403 Forbidden を返すミドルウェアです。
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;
class RestrictIpAddress
{
/**
* 許可するIPアドレスのリスト
* ※本番環境では config や .env から取得することを推奨
*/
protected array $allowedIps = [
'127.0.0.1',
'::1',
'192.168.1.0/24', // CIDR表記にも対応
];
public function handle(Request $request, Closure $next): Response
{
$clientIp = $request->ip();
// 許可リストに含まれているか判定
if (! $this->isIpAllowed($clientIp)) {
abort(403, 'アクセスが拒否されました。許可されていないIPアドレスです。');
}
return $next($request);
}
protected function isIpAllowed(?string $ip): bool
{
if (empty($ip)) {
return false;
}
foreach ($this->allowedIps as $allowedIp) {
if ($this->ipMatches($ip, $allowedIp)) {
return true;
}
}
return false;
}
protected function ipMatches(string $ip, string $allowedIp): bool
{
if ($ip === $allowedIp) {
return true;
}
// CIDRサブネットマスク判定
if (str_contains($allowedIp, '/')) {
[$subnet, $mask] = explode('/', $allowedIp);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return (ip2long($ip) & ~((1 << (32 - (int) $mask)) - 1)) === ip2long($subnet);
}
}
return false;
}
}
2. リクエスト処理時間計測&ログ記録ミドルウェア(性能監視)
リクエスト開始時刻と終了時刻を microtime(true) で計測し、一定時間(例: 500ミリ秒以上)を超えたスローリクエストを警告ログとして記録するミドルウェアです。
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesLog;
use SymfonyComponentHttpFoundationResponse;
class MeasureExecutionTime
{
public function handle(Request $request, Closure $next): Response
{
$startTime = microtime(true);
// コントローラー等の処理を実行
$response = $next($request);
$duration = (microtime(true) - $startTime) * 1000; // ミリ秒換算
$durationFormatted = number_format($duration, 2);
// レスポンスヘッダーに処理時間を追加(デバッグ・監視用)
$response->headers->set('X-Response-Time', "{$durationFormatted}ms");
// 500ms以上かかった処理はスロークエリ・ボトルネック調査用にWarningログ出力
if ($duration > 500) {
Log::warning('Slow Request Detected', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'duration_ms' => $durationFormatted,
'status_code' => $response->getStatusCode(),
'client_ip' => $request->ip(),
]);
}
return $response;
}
}
3. カスタムAPIヘッダー認証ミドルウェア(APIトークン検証)
モバイルアプリやマイクロサービス間連携などで、HTTPリクエストヘッダー X-Api-Key に正しいAPIキーが付与されているかを検証するミドルウェアです。
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;
class ValidateApiKey
{
public function handle(Request $request, Closure $next): Response
{
$apiKey = $request->header('X-Api-Key');
$validKey = config('services.api.secret_key');
if (empty($apiKey) || ! hash_equals((string) $validKey, (string) $apiKey)) {
return response()->json([
'error' => 'Unauthorized',
'message' => '無効または未指定のAPIキーです。',
], Response::HTTP_UNAUTHORIZED);
}
return $next($request);
}
}
4. セキュリティレスポンスヘッダー付与ミドルウェア
クリックジャッキング対策(X-Frame-Options)やMIMEスニッフィング防止(X-Content-Type-Options)などのセキュリティヘッダーをすべてのレスポンスに自動付与する後処理ミドルウェアです。
<?php
namespace AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;
class SecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// セキュリティヘッダーの付与
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
return $response;
}
}
Laravel 10以前(app/Http/Kernel.php)からの移行ポイントと注意点
以前のLaravelバージョンからアップグレードした開発者が最も戸惑うのが app/Http/Kernel.php の廃止です。対応関係を早見表にまとめました。
Kernel.php廃止に伴うコード書き換え早見表
| 従来のKernel.php(Laravel 10以前) | 最新のbootstrap/app.php(Laravel 11以降) |
|---|---|
$middleware(グローバル) |
$middleware->append(...) / $middleware->prepend(...) |
$middlewareGroups['web'] |
$middleware->web(append: [...]) |
$middlewareGroups['api'] |
$middleware->api(append: [...]) |
$middlewareAliases |
$middleware->alias(['key' => ...]) |
$middlewarePriority |
$middleware->priority([...]) |
| 除外・削除設定 | $middleware->remove(...) |
ミドルウェアが適用されない・エラーになる時のチェックリスト
自作ミドルウェアがうまく動かない場合は、以下の3点を確認してください。
- 設定キャッシュのクリアを実行したか?
bootstrap/app.phpやルーティングを変更した後は、設定キャッシュが残っていると反映されない場合があります。php artisan optimize:clear - エイリアス名とルーティング側での指定文字列が完全一致しているか?
$middleware->alias(['ip.check' => ...])と定義しているのに、ルーティング側で->middleware('check.ip')と書いていないかスペルミスを確認してください。 return $next($request);を書き忘れていないか?
前処理で検証に合格した場合、必ず$next($request)を呼び出してレスポンスを return する必要があります。忘れると画面が真っ白(500エラーまたは空レスポンス)になります。
まとめ|ミドルウェアを適切に設計して堅牢なアプリケーションを作ろう
本記事では、Laravelにおけるカスタムミドルウェアの自作手順から、Laravel 11以降の最新登録構文(bootstrap/app.php)、引数渡し、実務で使える4つのサンプルコードまで徹底解説しました。
- ミドルウェアの役割:HTTPリクエストとレスポンスのパイプラインに割り込み、認証やIP制限、ログなどの共通処理を一元化する。
- 作成コマンド:
php artisan make:middleware クラス名でapp/Http/Middleware/に即座に生成。 - 最新登録場所:
bootstrap/app.phpの->withMiddleware()内で、グローバル(append/prepend)、エイリアス(alias)、グループ(web/api)を設定。 - 柔軟な拡張性:引数(
role:admin)や終了処理(terminateメソッド)を組み合わせることで、高度な要件にもすっきり対応可能。
ミドルウェアを上手に活用することで、コントローラーをシンプルかつ清潔に保ち、セキュリティと保守性の高いLaravelアプリケーションを構築できます。ぜひ本記事のコードを参考に、プロジェクトで活用してみてください!

コメント