
31 January, 2025
Laravel Middleware: Guardians of Your Application
Luigi Laezza
5 minutes
Coding tips
Middleware in Laravel is a powerful mechanism that acts as a gatekeeper for incoming HTTP requests and outgoing responses. They provide a convenient way to filter and modify requests and responses, allowing you to execute specific logic before your application's core logic is executed or after a response is generated. This capability enables a wide range of functionality, from authentication and authorization to data manipulation and request logging.
In essence, middleware operates on the "request-response lifecycle." It intercepts the request, optionally processes it, and then either passes it along to the next middleware in the chain or directly to the route handler. Similarly, it can intercept the response, modify it, and then pass it on, ultimately reaching the client.
Key Concepts
The Middleware Pipeline: Laravel treats middleware as a pipeline. Requests pass through them in the order they are registered, both on their way in and out of the application.
Request Processing: Middleware can access and modify the incoming Illuminate\Http\Request object, enabling manipulation of headers, query parameters, request body, and more.
Response Manipulation: Middleware can also access and modify the outgoing Illuminate\Http\Response object before it's sent to the client, affecting status codes, headers, and the response content.
Termination: Middleware can choose to terminate the request cycle by returning a response, preventing subsequent middleware or the route handler from executing.
Global, Route, and Group Middleware:Laravel allows you to apply middleware at different levels, providing flexibility to match your application's needs.
Laravel 11 Enhancements
Laravel 11 introduces some refinements but the core middleware concepts remain consistent:
Simplified Skeleton: Laravel 11's streamlined application structure has slightly changed the directory structure, but the way middleware is defined and registered stays largely the same.
kernel.php: The kernel still plays a key role, with App\Http\Kernel containing the middleware setup.
Middleware Types
Before Middleware: These execute before the route handler is triggered, acting like pre-processors on the request.
After Middleware (Terminable): These execute after the route handler and the response has been generated, making them ideal for post-processing. These need to implement \Illuminate\Contracts\Foundation\TerminableMiddleware.
Creating Custom Middleware
You can create custom middleware using the Artisan command:
php artisan make:middleware CheckAge
This generates a new middleware file in app/Http/Middleware/CheckAge.php:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class CheckAge { /** * Handle an incoming request. * * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request, Closure $next): Response { if ($request->query('age') < 18) { return response('You are too young', 403); // Terminating the request } return $next($request); // Pass on to next middleware or the route handler } }
Explanation:
handle(Request $request, Closure $next): This is the main method where the middleware logic resides.
$request: The Illuminate\Http\Requestobject containing information about the incoming request.
$next: A callable that passes the request on to the next middleware or route handler.
return $next($request): This sends the request to the next in line.
return response('...');: Returning a response immediately terminates the middleware pipeline and the route handler.
Terminable Middleware (After Middleware)
For after middleware, the class must implement \Illuminate\Contracts\Foundation\TerminableMiddlewareand have terminate() method:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; use Illuminate\Contracts\Foundation\TerminableMiddleware; class LogResponse implements TerminableMiddleware { public function handle(Request $request, Closure $next): Response { return $next($request); } /** * Handle tasks after the response has been sent to the browser. */ public function terminate(Request $request, Response $response): void { // Log the status code and URL to a file file_put_contents( storage_path('logs/responses.log'), now()->toDateTimeString() . " - URL: " . $request->url() . ", Status: " . $response->getStatusCode() . "\n", FILE_APPEND ); } }
Explanation:
implements TerminableMiddleware: The class declares it as a terminable middleware.
terminate(Request $request, Response $response): Executes after the response is generated. You have both the request and the response object.
Registering Middleware
Middleware needs to be registered in app/Http/Kernel.php:
<?php namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { /** * The application's global HTTP middleware stack. * * These middleware are run during every request to your application. * * @var array<int, class-string|string> */ protected $middleware = [ \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\Http\Middleware\HandleCors::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Http\Middleware\TrustProxies::class, ]; /** * The application's route middleware groups. * * @var array<string, array<int, class-string|string>> */ protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\Http\Middleware\HandleCors::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Http\Middleware\VerifyCsrfToken::class, ], 'api' => [ \Illuminate\Http\Middleware\HandleCors::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\ThrottleRequests::class . ':api', ], ]; /** * The application's route middleware aliases. * * These middleware may be assigned to groups or used individually. * * @var array<string, class-string|string> */ protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'cache' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'CheckAge' => \App\Http\Middleware\CheckAge::class, 'LogResponse' => \App\Http\Middleware\LogResponse::class, ]; /** * The priority-sorted list of middleware. * * This forces non-global middleware to always run in the given list order. * * @var array<int, string> */ protected $middlewarePriority = [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\Authenticate::class, \Illuminate\Routing\Middleware\ThrottleRequests::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, \App\Http\Middleware\LogResponse::class, ]; }
Explanation:
$middleware: Global middleware that runs on every request.
$middlewareGroups: Groups of middleware that can be applied to routes. 'web' and 'api' are the default.
$routeMiddleware: Aliases for middleware allowing for easy use on individual routes or groups. We've added our custom middleware 'CheckAge' and 'LogResponse' here.
$middlewarePriority: Specify the order in which middleware runs. This is to resolve some dependencies between middleware, for example, ShareErrorsFromSession must execute after StartSession, and VerifyCsrfToken after ShareErrorsFromSession to work correctly. In this example, we've added \App\Http\Middleware\LogResponse::class, that will run after the standard Laravel middleware, and also can read the responseobject for logging.
$middlewareGroups: Groups of middleware that can be applied to routes. 'web' and 'api' are the default.
$routeMiddleware: Aliases for middleware allowing for easy use on individual routes or groups. We've added our custom middleware 'CheckAge' and 'LogResponse' here.
$middlewarePriority: Specify the order in which middleware runs. This is to resolve some dependencies between middleware, for example, ShareErrorsFromSession must execute after StartSession, and VerifyCsrfToken after ShareErrorsFromSession to work correctly. In this example, we've added \App\Http\Middleware\LogResponse::class, that will run after the standard Laravel middleware, and also can read the responseobject for logging.
Applying Middleware
Global Middleware: Added to $middlewarein Kernel.php.
Route Middleware: Used within route definitions or groups:
// In routes/web.php Route::get('/adult-content', function () { return 'Welcome to the adult content!'; })->middleware('CheckAge'); Route::middleware('LogResponse')->get('/log-test', function(){ return 'log me'; }); Route::group(['middleware' => ['auth', 'verified']], function () { Route::get('/verified-dashboard', function() { return 'verified access'; }); });
Coding Examples
Authentication Middleware (using Laravel's built-in middleware):
// routes/web.php Route::middleware('auth')->get('/profile', function () { return 'This page is only for authenticated users'; });
Authorization Middleware (custom):
// app/Http/Middleware/CheckUserRole.php <?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class CheckUserRole { public function handle(Request $request, Closure $next, string $role): Response { if (! auth()->check() || auth()->user()->role !== $role) { abort(403, 'Unauthorized for this role.'); } return $next($request); } }
Register the middleware alias in app/Http/Kernel.php:
'admin' => \App\Http\Middleware\CheckUserRole::class . ':admin',
Use it in routes:
// routes/web.php Route::middleware('admin')->get('/admin', function () { return 'Admin Panel'; });
Locale Middleware (handling language setting):
// app/Http/Middleware/SetLocale.php <?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Symfony\Component\HttpFoundation\Response; class SetLocale { public function handle(Request $request, Closure $next): Response { $locale = $request->query('locale'); if( $locale && in_array($locale, ['en', 'fr', 'es']) ) { App::setLocale($locale); } else { $locale = session('locale', config('app.locale')); App::setLocale($locale); } return $next($request); } }
Register:
'setLocale' => \App\Http\Middleware\SetLocale::class,
Use in a route:
Route::middleware('setLocale')->get('/welcome', function(){ return 'Welcome to my application'; });
This will detect a locale request param and override application locale. If there is no param, but there is an existing session, it will override locale with session's locale. If no session exists, it will fallback to the application default locale.
Advanced Topics
Middleware Parameters: Like in the authorization example above, you can pass parameters to your middleware via the colon syntax.
Route Groups: Combine middleware for related routes by using Route::group(['middleware' => [...], ...]);
Priority Ordering: The $middlewarePriority in Kernel.php allows you to prioritize execution of certain middleware. This ensures certain middleware executes in the right order.
Middleware for APIs: APIs often need specific middleware for authentication (e.g., API tokens, OAuth) and rate limiting.
Testing: Middleware should be thoroughly unit-tested to ensure proper functionality.
Conclusion
Middleware is a core component of Laravel, providing a powerful way to manage application flow and enforce complex business logic. By understanding the concepts of the pipeline, request and response manipulation, and the various types of middleware, you can build robust, maintainable, and secure Laravel applications.
By using the tools provided by Laravel 11 you can build middleware that perfectly matches your application requirements.
By using the tools provided by Laravel 11 you can build middleware that perfectly matches your application requirements.