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.

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 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.
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).
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. The auth middleware accepts a guard parameter to specify which authentication driver to use.
// 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.
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.
// 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.
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:
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.
Ready to ace your Laravel interviews?
Practice with our interactive simulators, flashcards, and technical tests.
Building Custom Middleware from Scratch
Creating custom middleware covers scenarios that built-in middleware does not handle. The make:middleware Artisan command scaffolds a new class with the correct structure.
php artisan make:middleware EnsureUserHasRoleRole-Based Access Control Middleware
A common custom middleware pattern enforces role-based authorization at the route level, accepting role names as parameters.
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:
->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:
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.
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 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.
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.
Maintenance mode bypass — allow internal IPs to access the application during maintenance:
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:
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).
Ready to ace your Laravel interviews?
Practice with our interactive simulators, flashcards, and technical tests.
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
authmiddleware protects routes with guard-based authentication, supporting multiple user types viaauth:guardsyntax - Rate limiting through
throttlemiddleware and namedRateLimiter::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.phpusing a fluent API, withprioritycontrolling execution order - Terminable middleware runs post-response tasks (analytics, logging) without impacting user-facing latency
- Middleware parameters via the
:paramsyntax keep route definitions expressive and middleware classes reusable across different contexts
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Tags
Share
Related articles

Eloquent ORM: Patterns and Optimizations for Laravel
Master Eloquent ORM with advanced patterns and optimization techniques. Eager loading, query scopes, accessors, mutators and performance for Laravel applications.

Laravel and PHP Interview Questions: Top 25 in 2026
The 25 most common Laravel and PHP interview questions. Eloquent ORM, middleware, artisan, queues, tests and architecture with detailed answers and code examples.

Laravel 11: Building a Complete Application from Scratch
Comprehensive guide to building a Laravel 11 application with authentication, REST API, Eloquent ORM, and deployment. Practical tutorial for beginners and intermediate developers.