Laravel Middleware Pipeline

·

·

Middleware wraps HTTP requests in layers. Each piece can inspect or modify the request before the controller runs and the response on the way out.

Middleware Basics and Registration

Laravel ships with middleware for CSRF, sessions, authentication, throttling, and maintenance mode. They are registered in bootstrap/app.php in Laravel 11.

Middleware either returns a response early (redirect to login) or passes $next($request) to continue the pipeline.

Step 1: Create custom middleware

php artisan make:middleware EnsureUserIsSubscribed generates handle(Request $request, Closure $next) logic. Return redirect or abort when checks fail.

Register alias in bootstrap/app.php using $middleware->alias(['subscribed' => EnsureUserIsSubscribed::class]) for short route references.

public function handle(Request $request, Closure $next)
{
    if (! $request->user()?->subscribed()) {
        return redirect('billing');
    }
    return $next($request);
}

Step 2: Route middleware assignment

Apply middleware on routes: Route::get('/dashboard', …)->middleware(['auth', 'verified', 'subscribed']);

Middleware runs in order listed. Put authentication before authorization middleware that assumes a logged-in user.

Route::middleware(['auth', 'subscribed'])->group(function () { ... });

Step 3: Middleware groups

web group enables cookies, session, CSRF, and bindings. api group applies throttle and bindings without sessions by default.

Append middleware to groups in bootstrap/app.php when every web route needs locale detection or tenant resolution.

$middleware->appendToGroup('web', SetLocale::class);

Step 4: Global middleware

TrustProxies, HandleCors, and PreventRequestsDuringMaintenance run on every request. Add custom global middleware sparingly because cost applies to all traffic.

Profile middleware in local development to ensure you are not decrypting cookies on health check endpoints unnecessarily.

$middleware->append(TrackRequestId::class);

Pipeline Order and Parameters

Middleware stacks execute like an onion: global, group, route, then controller middleware on the way in, reversed on the way out.

Pass parameters to middleware using alias:param syntax like role:admin or throttle:60,1.

Step 1: Middleware parameters

Define handle($request, Closure $next, string $role) and register Route::middleware('role:admin'). Split multiple params with commas.

Validate parameters against allowlists inside handle to prevent route typos from silently granting access.

->middleware('role:admin,manager')

Step 2: Substitute bindings middleware

SubstituteBindings resolves route model binding before controllers run. It lives in web and api groups automatically.

Custom binding logic in RouteServiceProvider executes before authorization middleware that needs resolved models.

Route::middleware('bindings')

Step 3: Priority and sorting

Middleware priority array ensures StartSession runs before ShareErrorsFromSession even when aliases are listed in different order.

Misordered session and CSRF middleware causes confusing token mismatch errors on first load.

$middleware->priority([...]);

Step 4: Skipping middleware

withoutMiddleware on route groups excludes specific classes for webhooks that cannot send CSRF tokens.

Document exceptions carefully; excluding CSRF should be paired with signature validation middleware.

Route::post('/webhook', WebhookController::class)
    ->withoutMiddleware(VerifyCsrfToken::class);

Terminable Middleware and Testing

Middleware implementing terminate() runs after the response is sent to the client, useful for logging response time or flushing metrics.

Test middleware by issuing HTTP requests in feature tests and asserting redirects or headers.

Step 1: Terminate method

public function terminate(Request $request, Response $response) { Log::info('request', ['ms' => …]); } executes after response ships.

Do not rely on terminate for critical business logic—clients may disconnect before terminate runs.

public function terminate($request, $response): void { ... }

Step 2: Before vs after middleware

Some concerns belong after controller execution, like adding security headers. Use handle by calling $response = $next($request) then modifying headers before return.

AddStrictTransportSecurity middleware often follows this pattern.

$response = $next($request);
return $response->header('X-Frame-Options', 'DENY');

Step 3: Testing middleware behavior

$this->get('/dashboard')->assertRedirect('/login') confirms auth middleware. actingAs skips login for authorized paths.

Use middleware only in tests with $this->withoutMiddleware() when isolating controller logic—not for skipping auth on protected routes you intend to test.

$this->actingAs($user)->get('/dashboard')->assertOk();

Step 4: CORS and API middleware

HandleCors reads config/cors.php for allowed origins on API routes. Adjust paths when SPA runs on a different subdomain.

Expose required headers for Sanctum SPA authentication including X-XSRF-TOKEN.

'paths' => ['api/*', 'sanctum/csrf-cookie'],

Aadyanex hardens Laravel middleware stacks with tenant resolution, observability hooks, and security headers tuned for production load balancers.