2026年9月12日

2026年9月12日

LaravelのMiddlewareの作成と活用方法|登録場所・引数・実例まで

Laravel の Middleware(ミドルウェア)は、リクエストがコントローラーに届く前後に共通処理を差し込む仕組みです。php artisan make:middleware でクラスを作り、Laravel 11 以降は bootstrap/app.php で登録してルートに付ければ使えます。この記事では、ミドルウェアの動き方、作成と登録の手順、引数の渡し方、セキュリティヘッダーや権限確認などの実用例、そして「効かない」ときの確認方法までを説明します。

ミドルウェアの役割と動く順番

ミドルウェアは、リクエストを受け取ってから次の処理(別のミドルウェアやコントローラー)へ渡す「関所」のようなものです。複数のミドルウェアは玉ねぎの皮のように重なっていて、リクエストは外側から内側へ、レスポンスは内側から外側へと通過します。

Laravel 自体も多くのミドルウェアを使っています。たとえば web グループには、Cookie の暗号化、セッションの開始、CSRF トークンの検証などが含まれます。ログインしていないユーザーをログイン画面へ移す auth、アクセス回数を制限する throttle も、組み込みのミドルウェアです。

自作するのが向いているのは、次のように「多くのルートで同じ処理を繰り返している」場面です。

  • 特定の役割を持つユーザーだけに管理画面を開放する
  • すべてのレスポンスにセキュリティ関連のヘッダーを付ける
  • メンテナンス中に社内の IP アドレスだけを通す
  • API リクエストの処理時間を記録する

逆に、特定の1画面だけで使う処理や、ビジネスロジックそのものはコントローラーやサービスクラスに置くほうが読みやすくなります。

ミドルウェアを作成する

次のコマンドで雛形を作ります。

php artisan make:middleware EnsureUserHasRole

app/Http/Middleware/EnsureUserHasRole.php が作られます。ここでは「指定した役割を持つユーザーだけを通す」ミドルウェアにします。

<?php

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 || ! in_array($user->role, $roles, true)) {
            abort(403);
        }

        return $next($request);
    }
}

handle() の第1引数がリクエスト、第2引数が「次の処理」を表すクロージャーです。条件を満たさなければ abort(403) で止め、満たせば $next($request) で先へ進めます。3番目以降の引数は、ルート側から渡す値です。この例では users テーブルに role カラムがある前提にしています。役割の管理が複雑になる場合は、Gate や Policy、または実績のある権限管理パッケージを使うほうが保守しやすくなります。

ミドルウェアを登録する

Laravel 11 以降は bootstrap/app.php

Laravel 11 からは app/Http/Kernel.php がなくなり、ミドルウェアの登録は bootstrap/app.php の withMiddleware() に集約されました。最新の雛形もこの構成を引き継いでいます(2026年10月現在)。

<?php

use App\Http\Middleware\EnsureUserHasRole;
use App\Http\Middleware\SecurityHeaders;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // 短い名前(エイリアス)で使えるようにする
        $middleware->alias([
            'role' => EnsureUserHasRole::class,
        ]);

        // web グループの末尾に追加する
        $middleware->web(append: [
            SecurityHeaders::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

すべてのリクエストに適用したいなら $middleware->append(...)、web や api グループに加えたいなら $middleware->web(append: [...]) や $middleware->api(append: [...]) を使います。既存のプロジェクトでは雛形のファイルをそのまま残し、withMiddleware() の中身だけを書き足してください。

Laravel 10 以前は Kernel.php

Laravel 10 以前のプロジェクトでは、app/Http/Kernel.php の $middleware(全体)、$middlewareGroups(グループ)、$middlewareAliases(エイリアス。古い版では $routeMiddleware)に登録します。11 へアップグレードしても、古い構成のまま動かし続けることができるため、自分のプロジェクトがどちらの構成かを先に確認しておくと迷いません。

ルートに適用する

use App\Http\Controllers\AdminController;

Route::middleware(['auth', 'role:admin,editor'])->group(function () {
    Route::get('/admin', [AdminController::class, 'index']);
});

role:admin,editor のようにコロンの後ろに書いた値が、handle() の ...$roles に ['admin', 'editor'] として渡されます。auth を先に書いているのは、未ログインのユーザーをまずログイン画面へ送るためです。順番を逆にすると、未ログインでも 403 が返ってしまいます。

グループの中で一部のルートだけ外したいときは ->withoutMiddleware(...) を使えます。ただし、CSRF 検証のようなセキュリティ上の処理を安易に外さないでください。

コントローラー単位で指定したい場合、Laravel 11 以降はコントローラーに HasMiddleware インターフェースを実装し、静的メソッド middleware() で指定します。

use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;

class ReportController extends Controller implements HasMiddleware
{
    public static function middleware(): array
    {
        return [
            'auth',
            new Middleware('role:admin', only: ['destroy']),
        ];
    }
}

実用例

レスポンスにセキュリティヘッダーを付ける

$next($request) の戻り値を受け取ってから処理すると、「後処理」のミドルウェアになります。

<?php

namespace App\Http\Middleware;

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

class SecurityHeaders
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');

        return $response;
    }
}

同じヘッダーを Nginx や Apache でも付けていると二重になるため、どちらで付けるかを決めて片方に寄せます。Content-Security-Policy は影響範囲が大きいので、まず Content-Security-Policy-Report-Only で様子を見てから有効にするのが無難です。

アクセス回数を制限する

回数制限は自作せず、組み込みの throttle を使えます。AppServiceProvider の boot() で制限を定義し、ルートに付けます。

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('contact', function (Request $request) {
        return Limit::perMinute(5)->by($request->ip());
    });
}
Route::post('/contact', [ContactController::class, 'send'])->middleware('throttle:contact');

レスポンス送信後に処理する

ミドルウェアに terminate(Request $request, Response $response): void メソッドを定義すると、レスポンスをブラウザへ返したあとに処理を実行できます(PHP-FPM などの環境が前提です)。重い処理であれば、ミドルウェアではなくキューのジョブに任せるほうが適しています。

効かないときの確認方法

起きやすい順に確認します。

  1. ルートに付いていない: php artisan route:list -v を実行すると、各ルートに適用されているミドルウェアが表示されます。まずここで、意図したミドルウェアが並んでいるかを見ます。
  2. エイリアスの登録漏れ: 「Target class [role] does not exist.」のようなエラーは、エイリアスが未登録か、綴りが違うことが原因です。bootstrap/app.php を確認します。
  3. ルートキャッシュが古い: php artisan route:cache を使っている場合、変更後に php artisan route:clear で消してから確認します。
  4. 順番の問題: ログインユーザーを参照するミドルウェアは、セッションを開始する web グループや auth より後に動く必要があります。順番を固定したいときは $middleware->priority([...]) で優先順位を指定できます。
  5. **$next($request) を返していない**: 戻り値を返し忘れると、空のレスポンスやエラーになります。

まとめ

  • ミドルウェアは、多くのルートに共通する前処理・後処理を1か所にまとめる仕組み
  • php artisan make:middleware で作成し、Laravel 11 以降は bootstrap/app.php の withMiddleware() で登録する
  • role:admin,editor のように引数を渡せ、auth などとの順番に意味がある
  • 回数制限は組み込みの throttle と RateLimiter::for() を使い、自作しない
  • 効かないときは php artisan route:list -v で適用状況を確認する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲