Uma Análise Profunda do Middleware Laravel
Middleware é o "pipeline de postos de verificação de segurança" do Laravel—cada requisição passa por camadas de verificações, como bagagem; as que falham são interceptadas imediatamente, enquanto as que passam podem prosseguir para a próxima etapa.
1. O Que Você Vai Aprender
- Fluxo de Execução do Middleware e o Padrão Pipeline
- Três métodos de registro: global, rota e grupo de middleware
- Middleware personalizado: Limite de Requisições / CORS / Resolução de Tenant
- Passagem de parâmetros no middleware: throttle:60,1 / role:admin
- Ordenação de Middleware e Controle de Prioridade
2. Uma História Real de um Arquiteto
(1) Dor: Todas as verificações de segurança estão concentradas no controller
Bob escreveu 10 linhas de código de validação no início de cada método do controller—verificações de autenticação, isolamento de tenant, limite de requisições, CORS e logging. 50 métodos x 10 linhas = 500 linhas de código duplicado. Quando Alice adicionou uma nova política de segurança, ela teve que fazer alterações em 50 lugares; ela perdeu três, o que levou a uma vulnerabilidade de segurança. Charlie perguntou: "Vocês já ouviram falar de middleware?"
(2) A Solução com Middleware
O middleware extrai a lógica de validação comum em classes separadas, e cada requisição passa automaticamente por múltiplas camadas de middleware—autenticação, limite de requisições, CORS e isolamento de tenant—enquanto os controllers focam apenas na lógica de negócio.
// Antes — 10 linhas de verificações em cada método
public function index() {
if (!auth()->check()) abort(401);
if (!tenant()->isActive()) abort(403);
if (RateLimiter::tooManyAttempts(...)) abort(429);
// ... finalmente, lógica de negócio
}
// Depois — middleware gerencia todas as verificações
Route::middleware(['auth', 'tenant.resolve', 'throttle:60,1'])
->get('/shops', [ShopController::class, 'index']);
// Controller tem apenas lógica de negócio
(3) Resultado
Depois que Bob implementou o middleware, o código do controller foi reduzido em 60%; a nova política de segurança de Alice exigiu modificar apenas um middleware, com zero falhas.
3. Fluxo de Execução do Pipeline de Middleware
(1) O Processo Completo de uma Requisição Passando pelo Middleware
flowchart LR
A[Requisição] --> B[MW Global 1]
B --> C[MW Global 2]
C --> D[MW de Rota 1]
D --> E[MW de Rota 2]
E --> F[Controller]
F --> G[Resposta]
G --> H[After MW 2]
H --> I[After MW 1]
I --> J[Cliente]
(2) O Modelo Cebola
Requisição →
Middleware A (antes) →
Middleware B (antes) →
Controller → Resposta
Middleware B (depois) →
Middleware A (depois) →
Resposta
Cada middleware pode:
- Before: Executar lógica antes da requisição chegar ao controller
- After: Executar lógica após o controller retornar uma resposta
- Short-circuit: Retornar uma resposta imediatamente sem passá-la para a próxima camada
(1) ▶ Exemplo: Entendendo o Modelo Cebola do Middleware
// app/Http/Middleware/LogRequests.php
class LogRequests
{
public function handle(Request $request, Closure $next): Response
{
// BEFORE — registrar requisição recebida
Log::info('Request:', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'ip' => $request->ip(),
]);
// Passar para o próximo middleware
$response = $next($request);
// AFTER — registrar status da resposta
Log::info('Response:', [
'status' => $response->getStatusCode(),
'duration' => defined('LARAVEL_START') ? round((microtime(true) - LARAVEL_START) * 1000) : null,
]);
return $response;
}
}
Saída:
// Execução bem-sucedida
4. Três Formas de Registro
(1) Middleware Global
Toda requisição é executada; não há necessidade de especificá-lo na rota.
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->append([
\App\Http\Middleware\LogRequests::class,
\App\Http\Middleware\SetLocale::class,
]);
$middleware->remove([
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
]);
})
(2) Middleware de Rota
Middleware especificado na definição da rota.
// Registrar alias
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'tenant' => \App\Http\Middleware\TenantResolve::class,
'role' => \App\Http\Middleware\CheckRole::class,
'ability' => \App\Http\Middleware\CheckAbility::class,
]);
})
// Usar nas rotas
Route::middleware(['auth', 'tenant', 'role:admin'])
->get('/admin/users', [AdminController::class, 'users']);
(3) Grupo de Middleware
Empacotar um conjunto de middleware e aplicá-lo como grupo.
| Nome do Grupo | Inclusões Padrão | Aplicável A |
|---|---|---|
web |
StartSession, EncryptCookies, VerifyCsrfToken | Roteamento Web |
api |
Throttle:api, SubstitueBindings | Roteamento de API |
// Personalizar grupos de middleware
->withMiddleware(function (Middleware $middleware) {
$middleware->appendToGroup('api', [
\App\Http\Middleware\TenantResolve::class,
]);
$middleware->prependToGroup('web', [
\App\Http\Middleware\SetLocale::class,
]);
})
| Método de Registro | Momento | Cenários Aplicáveis |
|---|---|---|
| Global | Todas as Requisições | Logs, CORS |
| Alias de Rota | Rota Especificada | Autenticação, Tenant |
| Grupo de Middleware | Rotas Intra-grupo | Grupo Web/API |
(1) ▶ Exemplo: Registro de Middleware do ShopMetrics
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
// Middleware global
$middleware->append([
\App\Http\Middleware\SetLocale::class,
]);
// Aliases
$middleware->alias([
'tenant' => \App\Http\Middleware\TenantResolve::class,
'role' => \App\Http\Middleware\CheckRole::class,
'ability' => \App\Http\Middleware\CheckAbility::class,
]);
// Adicionar ao grupo API
$middleware->appendToGroup('api', [
\App\Http\Middleware\EnsureJsonAccept::class,
]);
})
Saída:
// Execução bem-sucedida
5. Middleware Personalizado
(1) Criar middleware
php artisan make:middleware TenantResolve
php artisan make:middleware CheckRole
php artisan make:middleware EnsureJsonAccept
(2) Middleware TenantResolve
// app/Http/Middleware/TenantResolve.php
class TenantResolve
{
public function handle(Request $request, Closure $next): Response
{
$user = $request->user();
if (!$user || !$user->tenant_id) {
abort(403, 'No tenant associated with this account.');
}
$tenant = $user->tenant;
if ($tenant->status !== 'active') {
abort(403, 'Tenant account is ' . $tenant->status . '.');
}
// Armazenar tenant no container para escopo global
app()->instance(Tenant::class, $tenant);
// Definir contexto do tenant na requisição
$request->attributes->set('tenant', $tenant);
return $next($request);
}
}
(3) Middleware CORS
// app/Http/Middleware/HandleCors.php
class HandleCors
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('Access-Control-Allow-Origin', config('cors.allowed_origins'));
$response->headers->set('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Tenant-Id');
$response->headers->set('Access-Control-Max-Age', '86400');
if ($request->isMethod('OPTIONS')) {
$response->setStatusCode(204);
}
return $response;
}
}
(1) ▶ Exemplo: Middleware de Limite de Requisições do ShopMetrics
// app/Http/Middleware/PerTenantRateLimit.php
class PerTenantRateLimit
{
public function handle(Request $request, Closure $next, int $maxAttempts = 60, int $decayMinutes = 1): Response
{
$tenant = app()->make(Tenant::class);
$key = 'tenant:' . $tenant->id . ':' . $request->ip();
if (RateLimiter::tooManyAttempts($key, $maxAttempts)) {
$seconds = RateLimiter::availableIn($key);
abort(429, "Too many requests. Try again in {$seconds} seconds.");
}
RateLimiter::hit($key, $decayMinutes * 60);
$response = $next($request);
$response->headers->set('X-RateLimit-Limit', $maxAttempts);
$response->headers->set('X-RateLimit-Remaining', $maxAttempts - RateLimiter::attempts($key));
return $response;
}
}
Saída:
// Execução bem-sucedida
6. Parâmetros do Middleware
(1) Passagem de Parâmetros
// Definição de rota com parâmetros
Route::middleware('role:admin,super_admin')
->get('/admin/dashboard', [AdminController::class, 'dashboard']);
// Middleware recebe parâmetros
class CheckRole
{
public function handle(Request $request, Closure $next, string ...$roles): Response
{
if (!in_array($request->user()->role, $roles)) {
abort(403, 'Insufficient role. Required: ' . implode(', ', $roles));
}
return $next($request);
}
}
(2) O parâmetro throttle
// Throttle integrado com parâmetros
Route::middleware('throttle:60,1')->group(function () {
// 60 requisições por 1 minuto
});
Route::middleware('throttle:10,1')->group(function () {
// 10 requisições por 1 minuto (para endpoints custosos)
});
// Limitador de taxa personalizado
RateLimiter::for('api', function (Request $request) {
return $request->user()
? Limit::perMinute(60)->by($request->user()->id)
: Limit::perMinute(10)->by($request->ip());
});
RateLimiter::for('tenant-api', function (Request $request) {
$tenant = app()->make(Tenant::class);
$plan = $tenant->subscription?->plan;
return Limit::perMinute(match ($plan->slug ?? 'starter') {
'enterprise' => 300,
'pro' => 120,
default => 60,
})->by($tenant->id);
});
| Formato de Parâmetro | Descrição | Exemplo |
|---|---|---|
middleware:p1 |
Parâmetro único | role:admin |
middleware:p1,p2 |
Múltiplos Parâmetros | role:admin,editor |
throttle:max,decay |
Parâmetros de Throttling | throttle:60,1 |
(1) ▶ Exemplo: Limite de Requisições Baseado em Assinatura do ShopMetrics
// app/Providers/AppServiceProvider.php
public function boot(): void
{
RateLimiter::for('tenant-api', function (Request $request) {
$tenant = app()->make(Tenant::class);
$plan = $tenant->plan ?? null;
return Limit::perMinute(match ($plan->slug ?? 'starter') {
'enterprise' => 600,
'pro' => 120,
default => 60,
})->by($tenant->id)->response(function () {
return response()->json([
'message' => 'Rate limit exceeded. Upgrade your plan for higher limits.',
'upgrade_url' => route('pricing'),
], 429);
});
});
}
// routes/api.php
Route::middleware(['auth:sanctum', 'tenant', 'throttle:tenant-api'])
->prefix('v1')
->group(function () {
Route::apiResource('shops', Api\ShopController::class);
});
Saída:
// Execução bem-sucedida
7. Ordenação e Prioridade do Middleware
(1) Prioridade Padrão
O middleware é executado na ordem em que é registrado, mas certos middlewares devem ser executados primeiro (por exemplo, CORS deve ser processado antes da autenticação).
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->priority([
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TenantResolve::class, // Deve executar após CORS
\App\Http\Middleware\Authenticate::class,
]);
})
(2) Middleware terminable
// app/Http/Middleware/SendAnalyticsEvent.php
class SendAnalyticsEvent
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
// Executa APÓS a resposta ser enviada ao cliente
public function terminate(Request $request, Response $response): void
{
// Registro de analytics sem bloqueio
AnalyticsEvent::create([
'tenant_id' => app()->make(Tenant::class)?->id,
'path' => $request->path(),
'method' => $request->method(),
'status' => $response->getStatusCode(),
'duration_ms' => defined('LARAVEL_START')
? round((microtime(true) - LARAVEL_START) * 1000)
: null,
]);
}
}
| Abordagem de Ciclo de Vida | Momento | Propósito |
|---|---|---|
handle() (antes) |
Quando uma requisição é recebida | Autenticar, filtrar e modificar a requisição |
handle() (depois) |
Quando a resposta é retornada | Modificar os headers da resposta |
terminate() |
Após enviar uma resposta | Logs, Estatísticas, Limpeza |
(1) ▶ Exemplo: Configuração de Prioridade de Middleware do ShopMetrics
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
// Prioridade — CORS primeiro, auth antes do tenant
$middleware->priority([
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\TenantResolve::class,
\App\Http\Middleware\Authenticate::class,
\App\Http\Middleware\CheckRole::class,
]);
// Aliases
$middleware->alias([
'tenant' => \App\Http\Middleware\TenantResolve::class,
'role' => \App\Http\Middleware\CheckRole::class,
'tenant.throttle' => \App\Http\Middleware\PerTenantRateLimit::class,
]);
// Adições ao grupo API
$middleware->appendToGroup('api', [
\App\Http\Middleware\EnsureJsonAccept::class,
]);
});
Saída:
// Execução bem-sucedida
8. Exemplo Abrangente: A Arquitetura de Middleware do ShopMetrics
// ============================================
// Abrangente: Stack de Middleware do ShopMetrics
// Cobre: global, rota, grupos, params, prioridade, terminable
// ============================================
// app/Http/Middleware/TenantResolve.php
class TenantResolve
{
public function handle(Request $request, Closure $next): Response
{
$user = $request->user();
if (!$user?->tenant_id) {
abort(403, 'No tenant associated.');
}
$tenant = $user->tenant;
if ($tenant->status !== 'active') {
abort(403, 'Tenant is ' . $tenant->status);
}
app()->instance(Tenant::class, $tenant);
return $next($request);
}
}
// app/Http/Middleware/CheckRole.php
class CheckRole
{
public function handle(Request $request, Closure $next, string ...$roles): Response
{
if (!in_array($request->user()?->role, $roles)) {
abort(403, 'Required role: ' . implode('|', $roles));
}
return $next($request);
}
}
// app/Http/Middleware/EnsureSubscriptionActive.php
class EnsureSubscriptionActive
{
public function handle(Request $request, Closure $next): Response
{
$tenant = app()->make(Tenant::class);
$subscription = $tenant->subscription;
if (!$subscription || $subscription->status !== 'active') {
abort(402, 'Active subscription required.');
}
return $next($request);
}
}
// app/Http/Middleware/LogApiRequests.php
class LogApiRequests
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
public function terminate(Request $request, Response $response): void
{
ApiLog::create([
'tenant_id' => app()->make(Tenant::class)?->id,
'user_id' => $request->user()?->id,
'method' => $request->method(),
'path' => $request->path(),
'status' => $response->getStatusCode(),
'ip' => $request->ip(),
]);
}
}
// Rotas usando o stack completo de middleware
Route::middleware(['auth:sanctum', 'tenant', 'throttle:tenant-api', 'subscription.active'])
->prefix('v1')->group(function () {
Route::apiResource('shops', ShopController::class);
Route::middleware('role:tenant_owner,analyst')
->apiResource('analytics', AnalyticsController::class)->only(['index', 'show']);
Route::middleware('role:tenant_owner')
->apiResource('settings', SettingsController::class);
});
❓ Perguntas Frequentes
terminate é executado?register_shutdown_function). É adequado para operações como logging e estatísticas que não precisam afetar a resposta. terminate não bloqueia o usuário.return response()->json(['error' => 'Unauthorized'], 401);. O middleware e controllers subsequentes não serão executados.$middleware->skipWhen(callback) ou $request->is('api/*') dentro do middleware para determinar se deve pular uma rota. Não é recomendado incluir verificações de rota no middleware global.throttle:nome_da_estratégia.📖 Resumo
- O middleware executa segundo o modelo cebola: before → next → after
- Três métodos de registro: alias global, de rota e grupo de middleware
- O middleware personalizado é criado usando
make:middlewaree usado após registrar um alias - Os parâmetros do middleware são separados por dois pontos:
role:admin,editor - A prioridade determina a ordem de execução; CORS deve ter precedência sobre autenticação
terminate()é executado após a resposta ser enviada; é adequado para logging e estatísticas
📝 Exercícios
-
Exercício Básico (⭐): Crie um middleware TenantResolve que resolva o tenant a partir de um usuário autenticado e o armazene em um container, depois use-o em um grupo de rotas de API. Teste que usuários sem um tenant vinculado recebem um erro 403 ao tentar acessar a API.
-
Exercício Avançado (⭐⭐): Implemente o middleware PerTenantRateLimit para impor limite de requisições baseado no ID do tenant e endereço IP, definindo limites de taxa dinamicamente de acordo com o plano de assinatura (Starter: 60/min, Pro: 120/min, Enterprise: 600/min).
-
Desafio (⭐⭐⭐): Desenvolva um sistema completo de prioridade de middleware—CORS → TenantResolve → Auth → SubscriptionActive → CheckRole → throttle—para garantir que cada middleware execute no momento correto. Escreva testes para verificar que requisições de pre-check OPTIONS não são interceptadas pela autenticação.



