Laravel Middleware Uitgelicht: Authenticatie, Rate Limiting en Eigen Middleware
Uitgebreide handleiding over Laravel middleware met praktische voorbeelden voor authenticatie, rate limiting met throttle, eigen middleware bouwen en geavanceerde patronen voor productieomgevingen.

Middleware vormt een van de meest krachtige mechanismen binnen het Laravel-framework. Elk HTTP-verzoek dat een Laravel-applicatie bereikt, passeert een reeks middleware-lagen voordat het de bijbehorende controller bereikt. Deze lagen maken het mogelijk om authenticatie af te dwingen, verzoeken te beperken, invoer te valideren en beveiligingsheaders toe te voegen, zonder dat de controller zelf van deze verantwoordelijkheden op de hoogte hoeft te zijn. Dit artikel biedt een diepgaande verkenning van de middleware-pipeline in Laravel, van het ingebouwde authenticatie- en rate limiting-systeem tot het bouwen en registreren van volledig eigen middleware.
Dit artikel gaat ervan uit dat de lezer beschikt over basiskennis van Laravel-routing en de service container. Ervaring met het opzetten van authenticatie via Laravel Breeze of Fortify is nuttig, maar niet strikt noodzakelijk. De codevoorbeelden zijn gebaseerd op Laravel 11 en 12, die gebruikmaken van de vereenvoudigde middlewareconfiguratie in bootstrap/app.php.
Hoe de middleware-pipeline werkt
De middleware-pipeline van Laravel volgt het principe van een "ui-structuur" (onion architecture). Wanneer een HTTP-verzoek binnenkomt, passeert het achtereenvolgens elke middleware-laag, bereikt vervolgens de kern -- de controller -- en keert daarna terug door dezelfde lagen in omgekeerde volgorde. Dit tweerichtingsverkeer maakt het mogelijk om logica zowel voor als na de verwerking van het verzoek uit te voeren.
Een concreet voorbeeld verduidelijkt dit patroon. De volgende middleware meet de totale verwerkingstijd van een verzoek door een tijdstempel vast te leggen voordat het verzoek wordt doorgestuurd, en de verstreken tijd te loggen nadat het antwoord is gegenereerd:
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
}
}De $next($request)-aanroep vormt het scharnierpunt. Alle code die ervoor staat, wordt uitgevoerd op weg naar de controller (het "inkomende" gedeelte). Alle code die erna staat, wordt uitgevoerd op de terugweg, wanneer het antwoord al beschikbaar is. Dit maakt het mogelijk om in een enkele middleware zowel het verzoek als het antwoord te inspecteren of te wijzigen.
Authenticatiemiddleware
Authenticatie is verreweg de meest toegepaste middleware in Laravel-applicaties. Het framework biedt de auth-middleware die controleert of een gebruiker is ingelogd en, indien niet, het verzoek omleidt naar de inlogpagina of een 401-statuscode retourneert.
Het beschermen van routes met authenticatiemiddleware kan op twee manieren: per individuele route of via een routegroep die meerdere routes tegelijk afschermt.
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']);
});De groepsaanpak verdient de voorkeur wanneer meerdere routes dezelfde bescherming nodig hebben. Het voorkomt herhaling en maakt de routestructuur overzichtelijker.
Meerdere guards gebruiken
Laravel ondersteunt meerdere authenticatieguards, waarmee verschillende delen van de applicatie verschillende authenticatiemechanismen kunnen hanteren. API-routes maken doorgaans gebruik van tokengebaseerde authenticatie via Sanctum, terwijl het beheerderspaneel een aparte guard kan gebruiken met strengere toegangscontrole.
// 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']);
});De guard wordt opgegeven als parameter na de dubbele punt in de middleware-naam. Laravel controleert vervolgens de authenticatiestatus via de opgegeven guard in plaats van de standaard webguard.
Naast auth biedt Laravel ook de guest-middleware, die precies het omgekeerde doet: alleen niet-ingelogde gebruikers mogen de route bereiken. Dit is bijzonder nuttig voor login- en registratiepagina's. Een ingelogde gebruiker die naar /login navigeert, wordt automatisch doorgestuurd naar het dashboard in plaats van het inlogformulier opnieuw te zien.
Rate limiting met throttle
Rate limiting beschermt een applicatie tegen misbruik door het aantal verzoeken per tijdseenheid te beperken. Laravel biedt hiervoor de throttle-middleware, die standaard het aantal verzoeken per minuut begrenst op basis van het IP-adres of de geauthenticeerde gebruiker.
De eenvoudigste configuratie specificeert het maximale aantal verzoeken en de tijdsperiode in minuten:
// 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']);
});Het onderscheid tussen lees- en schrijfoperaties is een gangbare praktijk. Leesoperaties zoals het ophalen van berichten kunnen een hogere limiet verdragen, terwijl schrijfoperaties strenger worden beperkt om misbruik te voorkomen.
Benoemde rate limiters
Voor geavanceerdere scenario's biedt de RateLimiter-facade de mogelijkheid om benoemde limiters te definieren met dynamische logica. Hiermee kunnen limieten worden afgestemd op het type gebruiker, het abonnementsniveau of andere criteria.
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);
});
});
}De api-limiter in het bovenstaande voorbeeld past een gelaagd systeem toe: enterprisegebruikers krijgen 500 verzoeken per minuut, reguliere geauthenticeerde gebruikers 100, en anonieme bezoekers slechts 20. De login-limiter beperkt inlogpogingen tot vijf per minuut per IP-adres en retourneert een aangepaste JSON-foutmelding wanneer de limiet wordt overschreden.
Het toepassen van deze benoemde limiters op routes is eenvoudig:
Route::middleware('throttle:api')->group(function () {
Route::apiResource('/posts', PostController::class);
});
// routes/web.php
Route::middleware('throttle:login')
->post('/login', [AuthController::class, 'login']);In plaats van harde getallen worden nu de namen van de gedefinieerde limiters gebruikt. Dit maakt de routedefinities leesbaarder en centraliseert de rate limiting-logica op een enkele plek.
Klaar om je Laravel gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Eigen middleware bouwen
Naast de ingebouwde middleware biedt Laravel volledige vrijheid om eigen middleware te ontwikkelen voor applicatiespecifieke logica. Het aanmaken van een nieuw middlewarebestand gebeurt via het Artisan-commando:
php artisan make:middleware EnsureUserHasRoleDit genereert een skelet in app/Http/Middleware/ met de vereiste handle-methode. Van daaruit kan de middleware naar wens worden ingevuld.
Rolgebaseerde middleware
Een veelvoorkomend gebruik van eigen middleware is autorisatie op basis van gebruikersrollen. De volgende middleware accepteert een variabel aantal rollen als parameters en blokkeert de toegang wanneer de ingelogde gebruiker geen van de vereiste rollen bezit:
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);
}
}De ...$roles-parameter (variadic) maakt het mogelijk om meerdere rollen op te geven via de routedefinitie, gescheiden door komma's. De middleware controleert of de gebruiker tenminste een van de opgegeven rollen heeft.
De registratie en het gebruik van deze middleware ziet er als volgt uit:
->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);
});API-verzoeksanering
Een ander praktisch voorbeeld is middleware die binnenkomende API-verzoeken valideert en opschoont. De volgende middleware wijst niet-JSON-verzoeken af en verwijdert overtollige witruimte uit alle invoervelden:
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);
}
}Deze middleware combineert twee verantwoordelijkheden die samen een zinvolle eenheid vormen: het afdwingen van het juiste inhoudstype en het normaliseren van de invoer. Door dit als middleware te implementeren, hoeven individuele controllers zich niet met deze controles bezig te houden.
Middlewareregistratie in Laravel 12
Sinds Laravel 11 is de middlewareregistratie verplaatst van de traditionele Kernel.php naar bootstrap/app.php. Deze vereenvoudigde aanpak bundelt alle middlewareconfiguratie op een enkele, overzichtelijke locatie.
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();De configuratie onderscheidt vier niveaus. Globale middleware via append() wordt bij elk verzoek uitgevoerd. Groepsmiddleware via web() en api() geldt alleen voor routes binnen de betreffende groep. Aliassen via alias() registreren middleware onder een korte naam voor gebruik in routedefinities. De prioriteitslijst via priority() bepaalt de volgorde waarin middleware wordt uitgevoerd, ongeacht de volgorde waarin ze zijn geregistreerd.
De volgorde van middleware-uitvoering kan subtiele maar ernstige fouten veroorzaken. Authenticatiemiddleware moet altijd worden uitgevoerd nadat de sessie is gestart, anders is er geen sessie-informatie beschikbaar om de gebruiker te identificeren. Rolgebaseerde middleware moet na authenticatie worden uitgevoerd, omdat anders de gebruiker nog niet bekend is. De priority()-methode garandeert deze volgorde, zelfs wanneer middleware in een andere volgorde op routes wordt toegepast.
Terminable middleware
Standaard voert middleware logica uit voordat het antwoord naar de client wordt verzonden. Terminable middleware biedt echter de mogelijkheid om werk uit te voeren nadat het antwoord al is verzonden. Dit is ideaal voor taken die de eindgebruiker niet hoeft af te wachten, zoals het vastleggen van analytische gegevens of het bijwerken van statistieken.
Een middleware die analytische gegevens verzamelt zonder de responstijd te beinvloeden:
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(),
]);
}
}De handle-methode stuurt het verzoek direct door zonder vertraging. De terminate-methode wordt pas aangeroepen nadat het antwoord volledig naar de browser is verzonden. De database-insert in terminate voegt dus geen milliseconde toe aan de wachttijd van de gebruiker. Dit patroon is bijzonder waardevol voor logging, analytics en elke vorm van achtergrondregistratie die niet tijdkritisch is.
Productiepatronen
In productieomgevingen komen bepaalde middleware-patronen regelmatig terug. Twee van de meest voorkomende zijn onderhoudsmodus-bypass en beveiligingsheaders.
Onderhoudsmodus-bypass
Wanneer een applicatie in onderhoudsmodus staat, moeten ontwikkelaars en beheerders vaak wel toegang behouden. De volgende middleware staat verzoeken vanuit specifieke IP-adressen toe, zelfs tijdens onderhoud:
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);
}
}In een productieomgeving verdient het de aanbeveling om de toegestane IP-adressen via een omgevingsvariabele te configureren in plaats van ze hardcoded in de middleware op te nemen. Dit maakt aanpassingen mogelijk zonder codewijzigingen.
Beveiligingsheaders
Het toevoegen van beveiligingsheaders aan elke response is een eenvoudige maar effectieve maatregel tegen veelvoorkomende webkwetsbaarheden. De volgende middleware voegt een reeks standaard beveiligingsheaders toe:
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;
}
}De header X-Content-Type-Options: nosniff voorkomt dat browsers het inhoudstype van een response raden, wat MIME-type-aanvallen tegengaat. X-Frame-Options: SAMEORIGIN beschermt tegen clickjacking door het inbedden van de pagina in frames van andere domeinen te blokkeren. Strict-Transport-Security dwingt HTTPS af voor alle toekomstige verzoeken gedurende een jaar. Deze middleware wordt doorgaans als globale middleware geregistreerd zodat elke response deze headers bevat.
Klaar om je Laravel gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Conclusie
Middleware vormt het fundament van verzoekverwerking in Laravel en biedt een elegant mechanisme om doorsnijdende belangen los te koppelen van de kernlogica van een applicatie. De belangrijkste inzichten uit dit artikel:
- De middleware-pipeline volgt het ui-model: verzoeken bewegen naar binnen door elke laag, bereiken de controller, en antwoorden keren terug door dezelfde lagen in omgekeerde volgorde
- Authenticatiemiddleware (
auth,auth:sanctum,auth:admin) beschermt routes tegen ongeautoriseerde toegang, met ondersteuning voor meerdere guards die elk hun eigen authenticatiemechanisme hanteren - Rate limiting via de
throttle-middleware en deRateLimiter-facade biedt flexibele configuratie op basis van gebruikerstype, abonnementsniveau of IP-adres, inclusief aangepaste foutmeldingen - Eigen middleware wordt aangemaakt via
php artisan make:middlewareen kan logica uitvoeren voor de controller, na de controller of na het verzenden van het antwoord via determinate-methode - Middlewareregistratie in Laravel 12 vindt plaats in
bootstrap/app.php, waar globale middleware, groepsmiddleware, aliassen en uitvoeringsprioriteit centraal worden geconfigureerd - Terminable middleware maakt het mogelijk om niet-tijdkritieke taken zoals analytics en logging uit te voeren zonder de responstijd te beinvloeden
- Productiepatronen zoals onderhoudsmodus-bypass en beveiligingsheaders behoren tot de standaarduitrusting van elke professionele Laravel-applicatie
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Tags
Delen
Gerelateerde artikelen

Laravel en PHP sollicitatievragen: de Top 25 in 2026
De 25 meest gestelde Laravel- en PHP-sollicitatievragen. Eloquent ORM, middleware, Artisan, queues, tests en architectuur met uitgebreide antwoorden en codevoorbeelden.

Laravel 11: Een complete applicatie bouwen vanaf nul
Uitgebreide handleiding voor Laravel 11: installatie, Eloquent-modellen, authenticatie met Breeze, controllers, routes, autorisatie met policies, REST API met Sanctum, testen met Pest en productie-deployment.

Symfony 7: API Platform en Best Practices
Volledige gids voor het bouwen van professionele REST API's met Symfony 7 en API Platform 4. State Providers, Processors, validatie en serialisatie uitgelegd met praktische voorbeelden.