# Laravel Middleware Deep Dive: Authentication, Rate Limiting and Custom Middleware > Master Laravel middleware with practical examples covering authentication guards, rate limiting with throttle, custom middleware creation, and advanced patterns for production applications. - Published: 2026-04-16 - Updated: 2026-04-16 - Author: SharpSkill - Tags: laravel, middleware, authentication, rate-limiting, php - Reading time: 10 min --- Laravel middleware acts as a filtering layer between incoming HTTP requests and the application logic. Every request passes through a pipeline of middleware classes before reaching a controller, and every response travels back through the same pipeline. Understanding this mechanism is essential for building secure, performant Laravel applications in 2026. > **Middleware in a Nutshell** > > Middleware intercepts HTTP requests before they reach routes. Laravel 12 registers all middleware in `bootstrap/app.php` using a fluent API. Built-in middleware handles authentication, CSRF protection, session management, and rate limiting out of the box. ## How the Laravel Middleware Pipeline Works The Laravel HTTP kernel processes every request through a stack of middleware. Each middleware receives the request, performs its logic, and either passes the request to the next layer via `$next($request)` or short-circuits the pipeline by returning a response directly. This architecture follows the Chain of Responsibility pattern. Middleware can act **before** the request reaches the controller (e.g., authentication checks), **after** the response is generated (e.g., adding headers), or both. ```php // app/Http/Middleware/LogRequestTime.php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Symfony\Component\HttpFoundation\Response; 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 } } ``` This middleware wraps the request: it records the start time **before** processing and logs the duration **after** the response comes back. This before/after pattern is central to how middleware operates. ## Authentication Middleware: Protecting Routes Laravel ships with the `auth` middleware alias, mapped to `Illuminate\Auth\Middleware\Authenticate`. Applying it to a route ensures only authenticated users can access it. Unauthenticated users receive a 401 response (API) or get redirected to the login page (web). ```php // routes/web.php use App\Http\Controllers\DashboardController; use App\Http\Controllers\ProfileController; // 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']); }); ``` ### Multi-Guard Authentication Applications with multiple user types (admin panel, customer area, API) benefit from [guard-based authentication](/technologies/laravel/interview-questions/authentication). The `auth` middleware accepts a guard parameter to specify which authentication driver to use. ```php // routes/api.php // 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']); }); ``` The guard parameter after the colon tells Laravel which authentication configuration to verify against. This keeps authentication logic clean and separated across different parts of the application. > **Guest Middleware** > > The `guest` middleware is the inverse of `auth` — it only allows unauthenticated users through. Applying it to login and registration routes prevents already-authenticated users from accessing those pages. ## Rate Limiting with Throttle Middleware Laravel middleware rate limiting protects routes from abuse using the built-in `throttle` middleware. The simplest form accepts two parameters: the maximum number of requests and the time window in minutes. ```php // routes/api.php // 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']); }); ``` ### Named Rate Limiters for Advanced Control Defining named rate limiters in `AppServiceProvider` gives fine-grained control over limits based on user context. This approach is more flexible than inline throttle parameters. ```php // app/Providers/AppServiceProvider.php use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Http\Request; 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); }); }); } ``` Applying named limiters to routes uses the `throttle:name` syntax: ```php // routes/api.php Route::middleware('throttle:api')->group(function () { Route::apiResource('/posts', PostController::class); }); // routes/web.php Route::middleware('throttle:login') ->post('/login', [AuthController::class, 'login']); ``` The tiered rate limiter above demonstrates a production pattern: enterprise users get higher limits, authenticated users get moderate limits, and anonymous requests are throttled aggressively. The `by()` method determines the rate limit key — using user ID for authenticated users and IP address as a fallback. ## Building Custom Middleware from Scratch Creating custom [middleware](/technologies/laravel/interview-questions/middleware) covers scenarios that built-in middleware does not handle. The `make:middleware` Artisan command scaffolds a new class with the correct structure. ```bash php artisan make:middleware EnsureUserHasRole ``` ### Role-Based Access Control Middleware A common custom middleware pattern enforces [role-based authorization](/technologies/laravel/interview-questions/authorization-policies) at the route level, accepting role names as parameters. ```php // app/Http/Middleware/EnsureUserHasRole.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 || ! $user->hasAnyRole($roles)) { abort(403, 'Insufficient permissions.'); } return $next($request); } } ``` The variadic `...$roles` parameter allows passing multiple roles separated by commas. Registration and usage look like this: ```php // bootstrap/app.php ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ 'role' => \App\Http\Middleware\EnsureUserHasRole::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); }); ``` ### Request Transformation Middleware Middleware can modify the request before it reaches the controller. A JSON API middleware that enforces content type headers and trims string inputs: ```php // app/Http/Middleware/ApiRequestSanitizer.php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; 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); } } ``` This middleware handles two concerns: it validates the content type for POST requests and sanitizes all string inputs by trimming whitespace. ## Middleware Registration in Laravel 12 Laravel 12 centralizes all middleware registration in `bootstrap/app.php`. This replaced the older `app/Http/Kernel.php` approach that existed before Laravel 11. ```php // bootstrap/app.php use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Middleware; return Application::configure(basePath: dirname(__DIR__)) ->withMiddleware(function (Middleware $middleware) { // Global middleware (runs on every request) $middleware->append( \App\Http\Middleware\LogRequestTime::class ); // Add to the 'web' middleware group $middleware->web(append: [ \App\Http\Middleware\TrackPageViews::class, ]); // Add to the 'api' middleware group $middleware->api(prepend: [ \App\Http\Middleware\ApiRequestSanitizer::class, ]); // Register aliases for route-level use $middleware->alias([ 'role' => \App\Http\Middleware\EnsureUserHasRole::class, 'subscribed' => \App\Http\Middleware\EnsureUserIsSubscribed::class, ]); // Control execution order $middleware->priority([ \Illuminate\Session\Middleware\StartSession::class, \Illuminate\Auth\Middleware\Authenticate::class, \App\Http\Middleware\EnsureUserHasRole::class, ]); }) ->create(); ``` The `priority` array matters when multiple middleware are assigned to the same route. Laravel sorts them according to this list, ensuring session is started before authentication runs, and authentication completes before role checks. > **Middleware Execution Order** > > Middleware runs in the order registered. For route-level middleware, the `priority` array overrides the default order. Always place authentication before authorization middleware to avoid checking roles on unauthenticated requests. ## Terminable Middleware for Post-Response Tasks Terminable middleware executes logic **after** the response has been sent to the client. This is useful for logging, analytics, or cleanup tasks that should not block the user. ```php // app/Http/Middleware/CollectAnalytics.php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Symfony\Component\HttpFoundation\Response; 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(), ]); } } ``` The `terminate` method receives both the original request and the final response. Register the middleware as a singleton in `AppServiceProvider` to ensure the same instance handles both `handle()` and `terminate()`. ## Practical Middleware Patterns for Production Several middleware patterns appear consistently in production [Laravel applications](/blog/laravel/laravel-11-building-complete-application). **Maintenance mode bypass** — allow internal IPs to access the application during maintenance: ```php // app/Http/Middleware/MaintenanceBypass.php 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); } } ``` **Security headers** — add HSTS, content security policy, and other headers to every response: ```php // app/Http/Middleware/SecurityHeaders.php 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; } } ``` These patterns demonstrate the two primary middleware positions: **before** the request (maintenance bypass checks the IP and potentially blocks) and **after** the response (security headers modify the outgoing response). ## Conclusion - Laravel middleware operates as a pipeline: each class processes the request, acts on it, and passes it forward or short-circuits with a response - The `auth` middleware protects routes with guard-based authentication, supporting multiple user types via `auth:guard` syntax - Rate limiting through `throttle` middleware and named `RateLimiter::for()` definitions enables tiered access control based on user context - Custom middleware handles cross-cutting concerns like role checks, request sanitization, and security headers without cluttering controllers - All middleware registration in Laravel 12 happens in `bootstrap/app.php` using a fluent API, with `priority` controlling execution order - Terminable middleware runs post-response tasks (analytics, logging) without impacting user-facing latency - Middleware parameters via the `:param` syntax keep route definitions expressive and middleware classes reusable across different contexts --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/laravel/laravel-middleware-deep-dive