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, PHP attributes in Laravel 13, 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.
Middleware intercepts HTTP requests before they reach routes. Laravel 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 AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesLog;
use SymfonyComponentHttpFoundationResponse;
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 IlluminateAuthMiddlewareAuthenticate. 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 AppHttpControllersDashboardController;
use AppHttpControllersProfileController;
// 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. See the official rate limiting documentation for additional options.
use IlluminateCacheRateLimitingLimit;
use IlluminateSupportFacadesRateLimiter;
use IlluminateHttpRequest;
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 AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;
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' => AppHttpMiddlewareEnsureUserHasRole::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 AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;
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 bootstrap/app.php
Laravel centralizes all middleware registration in bootstrap/app.php. This replaced the older app/Http/Kernel.php approach that existed before Laravel 11. The same fluent API works in Laravel 12 and 13.
use IlluminateFoundationApplication;
use IlluminateFoundationConfigurationMiddleware;
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
// Global middleware (runs on every request)
$middleware->append(
AppHttpMiddlewareLogRequestTime::class
);
// Add to the 'web' middleware group
$middleware->web(append: [
AppHttpMiddlewareTrackPageViews::class,
]);
// Add to the 'api' middleware group
$middleware->api(prepend: [
AppHttpMiddlewareApiRequestSanitizer::class,
]);
// Register aliases for route-level use
$middleware->alias([
'role' => AppHttpMiddlewareEnsureUserHasRole::class,
'subscribed' => AppHttpMiddlewareEnsureUserIsSubscribed::class,
]);
// Control execution order
$middleware->priority([
IlluminateSessionMiddlewareStartSession::class,
IlluminateAuthMiddlewareAuthenticate::class,
AppHttpMiddlewareEnsureUserHasRole::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.
PHP Attributes for Middleware in Laravel 13
Laravel 13 introduced the #[Middleware] PHP attribute for declaring middleware directly on controller classes and methods. This approach keeps middleware configuration colocated with the code it protects, making authorization rules easier to read and maintain.
namespace AppHttpControllers;
use AppModelsComment;
use AppModelsPost;
use IlluminateRoutingAttributesControllersAuthorize;
use IlluminateRoutingAttributesControllersMiddleware;
#[Middleware('auth')]
class CommentController
{
#[Middleware('subscribed')]
#[Authorize('create', [Comment::class, 'post'])]
public function store(Post $post)
{
// Only authenticated, subscribed users who can create comments reach here
}
public function index(Post $post)
{
// Still requires auth (from class-level attribute)
return $post->comments;
}
}The class-level #[Middleware('auth')] applies to all methods. Method-level attributes stack on top: store() requires both auth and subscribed. The #[Authorize] attribute integrates with Laravel's policy system, checking permissions before the method executes.
This attribute-based approach is optional. Route-file middleware and bootstrap/app.php registration remain fully supported. Teams that prefer explicit route definitions can continue using the fluent API; teams that want self-documenting controllers can adopt attributes.
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 AppHttpMiddleware;
use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesDB;
use SymfonyComponentHttpFoundationResponse;
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 allows 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.
Sources
- Laravel Middleware Documentation - Official reference for middleware registration, groups, and parameters
- Laravel 13 Release Notes - Announcement of the
#[Middleware]attribute andPreventRequestForgeryimprovements - Laravel Rate Limiting Documentation - RateLimiter facade, named limiters, and throttle middleware configuration
Laravel Middleware Patterns for Interview Preparation
- 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 happens in
bootstrap/app.phpusing a fluent API, withprioritycontrolling execution order - Laravel 13's
#[Middleware]attribute allows declaring middleware directly on controllers, keeping authorization rules colocated with handlers - 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.
Can you spot the bug in Laravel?
One real snippet, one hidden bug, one attempt a day. No account needed to try.

Written by
Anthony Fillion-MailletFounder of SharpSkill
Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.
Updated on September 21, 2026
Tags
Share
Related articles

PHP Laravel Developer Interview Questions 2026: Complete Preparation Guide
Master Laravel interview questions for 2026. From Eloquent ORM and Service Container to queues, testing, and Laravel 13 features, prepare for technical interviews with real-world examples.

Laravel Solutions: Advanced Patterns, Debugging and Interview Questions 2026
Master Laravel solutions with advanced architectural patterns, debugging techniques, and interview-ready knowledge. Covers service classes, repository pattern, debugging with Telescope, and common interview questions for Laravel developers.

Laravel 12 in 2026: New Features, Starter Kits and Interview Questions
Laravel 12 brings redesigned starter kits with React 19, Vue 3, Livewire 4, and WorkOS AuthKit. A complete guide covering new features, upgrade path, and key interview questions for 2026.