Middlewares
Todos os middlewares disponíveis no projeto — o que cada um faz, quando usar e como implementar nas suas rotas.
Um middleware é uma camada que intercepta a requisição HTTP antes de chegar ao Controller. Você pode encadear vários middlewares em uma rota — eles executam em ordem, e cada um decide se passa a requisição adiante ou retorna uma resposta imediatamente.
Como usar middlewares nas rotas
Auth
Use Src\Kernel\Auth para proteger rotas com uma linha. Sem imports longos, sem arrays complexos.
<?php
use Src\Kernel\Auth;
// Rota pública — sem proteção
$router->get('/api/produtos', [Controller::class, 'listar']);
// Rota privada — qualquer usuário logado
$router->get('/api/perfil', [Controller::class, 'perfil'], Auth::user());
// Rota admin — só admin_system
$router->post('/api/admin/produtos', [Controller::class, 'criar'], Auth::admin());
// Rota com rate limit (10 req/min) + auth
$router->post('/api/produtos', [Controller::class, 'criar'], Auth::user(limit: 10));
// Rota admin com rate limit + proteção de banco
$router->delete('/api/admin/produtos/{id}', [Controller::class, 'deletar'], Auth::admin(limit: 5, db: true));
// Rota de API (token machine-to-machine)
$router->post('/api/webhook', [Controller::class, 'receber'], Auth::api());
// Rota pública com conteúdo diferente para logados
$router->get('/api/feed', [Controller::class, 'feed'], Auth::optional());
// Só rate limit — sem autenticação
$router->post('/api/contato', [Controller::class, 'enviar'], Auth::limit(5));
Acessando o usuário no Controller
<?php
use Src\Kernel\Auth;
class ProdutoController
{
public function criar(Request $request): Response
{
// Obtém o usuário logado (ou null se não autenticado)
$user = Auth::current($request);
// Obtém só o ID
$userId = Auth::id($request);
// Obtém o nível de acesso
$role = Auth::role($request);
// Lança 401 se não autenticado, retorna o usuário se sim
$user = Auth::check($request);
// Lança 403 se não for admin_system
$user = Auth::checkAdmin($request);
// Lança 403 se não tiver um dos papéis informados
$user = Auth::checkRole($request, 'admin', 'moderador');
return Response::json(['criado_por' => $userId]);
}
}
Forma manual (avançado)
Se precisar de controle total, ainda é possível usar os middlewares diretamente:
<?php
use Src\Kernel\Middlewares\AuthHybridMiddleware;
use Src\Kernel\Middlewares\AdminOnlyMiddleware;
use Src\Kernel\Middlewares\RateLimitMiddleware;
$rateLimit = [RateLimitMiddleware::class, ['limit' => 5, 'window' => 60, 'key' => 'recurso.criar']];
$router->post('/api/admin/recurso', [Controller::class, 'criar'], [
$rateLimit,
AuthHybridMiddleware::class,
AdminOnlyMiddleware::class,
]);
Visão geral de todos os middlewares
| Middleware | Finalidade | Uso típico |
|---|---|---|
AuthHybridMiddleware | Valida JWT e injeta usuário autenticado | Qualquer rota que exige login |
AdminOnlyMiddleware | Exige nivel_acesso = admin_system | Rotas de gerenciamento admin |
ApiTokenMiddleware | Valida token JWT de API (JWT_API_SECRET) | Integrações machine-to-machine |
AuthCookieMiddleware | Valida JWT via cookie auth_token | Rotas de página com cookie de sessão |
AuthPageMiddleware | Auth para páginas HTML — redireciona em vez de 401 | Dashboard e páginas protegidas |
OptionalAuthHybridMiddleware | Tenta autenticar mas não bloqueia se não houver token | Rotas com conteúdo diferente para logados |
RateLimitMiddleware | Limita requisições por IP e usuário | Registro, login, qualquer rota pública |
CircuitBreakerMiddleware | Protege o banco de falhas em cascata | Rotas que dependem de banco ou serviços externos |
BotBlockerMiddleware | Bloqueia scanners e ferramentas maliciosas | Global — aplicado automaticamente pelo Kernel |
HttpsEnforcerMiddleware | Bloqueia HTTP quando HTTPS é obrigatório | Global — aplicado automaticamente em produção |
RouteProtectionMiddleware | Valida JWT com restrição por papel (role) | Rotas com múltiplos níveis de acesso |
SecurityHeadersMiddleware | Garante headers de segurança em toda resposta | Global — aplicado automaticamente pelo Kernel |
AuthHybridMiddleware
O middleware de autenticação principal. Valida o JWT enviado no header Authorization: Bearer TOKEN ou no cookie auth_token, verifica se o token não foi revogado (blacklist) e injeta o usuário autenticado na requisição.
Após passar por este middleware, o Controller pode acessar:
$request->attribute('auth_user') — objeto do usuário autenticado
$request->attribute('auth_payload') — payload decodificado do JWT
$request->attribute('token_signed_with_api_secret') — true se assinado com JWT_API_SECRET
use Src\Kernel\Middlewares\AuthHybridMiddleware;
// Rota que exige qualquer usuário logado
$router->get('/api/perfil', [MeuController::class, 'perfil'], [AuthHybridMiddleware::class]);
// No Controller — acessa o usuário autenticado
public function perfil(Request $request): Response
{
$usuario = $request->attribute('auth_user'); // objeto Usuario
$payload = $request->attribute('auth_payload'); // stdClass com sub, nivel_acesso, etc.
return Response::json(['nome' => $usuario->getNomeCompleto()]);
}
// Respostas possíveis:
// 401 — token ausente, expirado ou revogado
// 200 — usuário autenticado, requisição passa para o Controller
AdminOnlyMiddleware
Deve ser usado sempre após o AuthHybridMiddleware. Verifica se o usuário autenticado tem nivel_acesso = admin_system e se o token foi assinado com JWT_API_SECRET. Tokens de usuários comuns são rejeitados com 403.
use Src\Kernel\Middlewares\AuthHybridMiddleware;
use Src\Kernel\Middlewares\AdminOnlyMiddleware;
// Sempre use AuthHybridMiddleware ANTES do AdminOnlyMiddleware
$router->get('/api/admin/usuarios', [Controller::class, 'listar'], [
AuthHybridMiddleware::class, // 1º: valida o JWT
AdminOnlyMiddleware::class, // 2º: verifica se é admin_system
]);
// Respostas possíveis:
// 401 — token ausente ou inválido (AuthHybridMiddleware)
// 403 — usuário autenticado mas não é admin_system
// 200 — admin autenticado, requisição passa
RateLimitMiddleware
Limita o número de requisições por IP e por usuário autenticado em uma janela de tempo. Usa Redis quando disponível (distribuído entre múltiplos servidores) ou arquivo local como fallback. Adiciona headers X-RateLimit-* em todas as respostas.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
limit | int | 60 | Máximo de requisições por janela |
window | int | 60 | Janela de tempo em segundos |
key | string | URI da rota | Prefixo único para o contador (evita colisão entre rotas) |
user_limit | int | igual a limit | Limite específico por usuário autenticado |
use Src\Kernel\Middlewares\RateLimitMiddleware;
// 5 requisições por minuto por IP — ideal para registro
$registroLimit = [RateLimitMiddleware::class, [
'limit' => 5,
'window' => 60,
'key' => 'meu.registro',
]];
$router->post('/api/registrar', [Controller::class, 'criar'], [$registroLimit]);
// 10 req/min por IP, 3 por usuário autenticado
$loginLimit = [RateLimitMiddleware::class, [
'limit' => 10,
'window' => 60,
'key' => 'meu.login',
'user_limit' => 3,
]];
$router->post('/api/login', [Controller::class, 'login'], [$loginLimit]);
// Respostas possíveis:
// 429 — limite atingido, com header Retry-After indicando quando tentar novamente
// Headers adicionados em toda resposta:
// X-RateLimit-Limit: 10
// X-RateLimit-Remaining: 7
// X-RateLimit-Reset: 1712345678 (timestamp Unix)
CircuitBreakerMiddleware
Implementa o padrão Circuit Breaker para proteger o banco de dados e serviços externos de falhas em cascata. Tem três estados: CLOSED (normal), OPEN (bloqueado após muitas falhas) e HALF (testando recuperação após o cooldown).
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
service | string | 'default' | Nome do serviço monitorado (ex: 'database', 'email') |
threshold | int | 5 | Número de falhas consecutivas para abrir o circuito |
cooldown | int | 30 | Segundos aguardando antes de tentar novamente (estado HALF) |
use Src\Kernel\Middlewares\CircuitBreakerMiddleware;
// Protege rotas que dependem do banco — abre após 5 falhas, cooldown de 20s
$dbCircuit = [CircuitBreakerMiddleware::class, [
'service' => 'database',
'threshold' => 5,
'cooldown' => 20,
]];
$router->post('/api/registrar', [Controller::class, 'criar'], [$dbCircuit]);
// Protege chamadas a serviço externo (ex: API de pagamento)
$pagCircuit = [CircuitBreakerMiddleware::class, [
'service' => 'pagamento',
'threshold' => 3,
'cooldown' => 60,
]];
$router->post('/api/pagamento', [Controller::class, 'pagar'], [$pagCircuit]);
// Respostas possíveis:
// 503 — circuito aberto, com header Retry-After
// Passa normalmente quando CLOSED ou HALF
ApiTokenMiddleware
Valida tokens JWT gerados com JWT_API_SECRET (tokens de API, não de usuário). Usado para integrações machine-to-machine onde não há um usuário humano fazendo login. Aceita o token via header Authorization: Bearer TOKEN ou X-API-KEY: TOKEN.
use Src\Kernel\Middlewares\ApiTokenMiddleware;
// Rota acessível apenas por tokens de API (integrações, webhooks, etc.)
$router->post('/api/webhook/pagamento', [Controller::class, 'receber'], [ApiTokenMiddleware::class]);
// Respostas possíveis:
// 401 — token ausente ou inválido
// 403 — token válido mas não é de API (é de usuário)
// 500 — JWT_API_SECRET não configurado no .env
Como gerar o token JWT_API_SECRET
Rotas protegidas por AdminOnlyMiddleware ou ApiTokenMiddleware exigem um token assinado com JWT_API_SECRET. Há três formas de gerar esse token:
Opção 1 — Via CLI (recomendado)
O jeito mais simples. O sistema gera e exibe o token pronto para usar:
# Abre o menu interativo e escolha a opção 11
php vupi setup
# Ou direto, sem menu:
php vupi setup --auto --api-token=generate
O token gerado tem validade de 1 hora e payload:
{
"sub": "api_user_id",
"exp": 1712345678,
"api_access": true,
"tipo": "api"
}
Opção 2 — Via login do admin_system
Para rotas com AdminOnlyMiddleware, faça login com o usuário admin_system. O token retornado já é assinado com JWT_API_SECRET e funciona nessas rotas:
# curl
curl -X POST http://localhost:3005/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","senha":"admin123"}'
# HTTPie
http POST http://localhost:3005/api/auth/login \
[email protected] senha=admin123
// Resposta — copie o "access_token":
{
"status": "success",
"access_token": "eyJ...", // ← use este nas rotas admin
"token_type": "Bearer",
"expires_in": 900
}
// Use assim nas requisições:
// Authorization: Bearer eyJ...
Rotas com AdminOnlyMiddleware (ex: GET /api/usuarios) → use o token do login do admin_system via /api/auth/login.
Rotas com ApiTokenMiddleware (ex: webhooks, integrações) → use o token gerado via php vupi setup opção 11.
Rotas com AuthHybridMiddleware (ex: GET /api/perfil) → use o token de qualquer usuário logado via /api/login.
Opção 3 — Gerar manualmente em PHP
Útil para scripts de integração ou testes automatizados:
<?php
require __DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;
$secret = $_ENV['JWT_API_SECRET'] ?? 'seu_jwt_api_secret_aqui';
// Token de API (para ApiTokenMiddleware)
$tokenApi = JWT::encode([
'sub' => 'minha_integracao',
'exp' => time() + 3600, // expira em 1 hora
'api_access' => true,
'tipo' => 'api',
], $secret, 'HS256');
echo "Token de API:\n" . $tokenApi . "\n";
O JWT_API_SECRET fica no .env e nunca deve ser commitado no repositório. Tokens gerados com ele têm acesso total às rotas admin — trate-os como senhas.
OptionalAuthHybridMiddleware
Tenta autenticar o usuário, mas não bloqueia se o token estiver ausente ou inválido. Útil para rotas que retornam conteúdo diferente para usuários logados e não logados — como um feed público que mostra mais detalhes para quem está autenticado.
use Src\Kernel\Middlewares\OptionalAuthHybridMiddleware;
// Rota pública, mas com conteúdo extra para usuários logados
$router->get('/api/feed', [Controller::class, 'feed'], [OptionalAuthHybridMiddleware::class]);
// No Controller — verifica se o usuário está autenticado
public function feed(Request $request): Response
{
$usuario = $request->attribute('auth_user'); // null se não autenticado
if ($usuario !== null) {
// Retorna feed personalizado para o usuário logado
return Response::json(['feed' => $this->feedPersonalizado($usuario)]);
}
// Retorna feed público genérico
return Response::json(['feed' => $this->feedPublico()]);
}
AuthCookieMiddleware
Variante do AuthHybridMiddleware que lê o token exclusivamente do cookie auth_token (em vez do header Authorization). Usado internamente pelo sistema para rotas de página que dependem de cookie de sessão.
use Src\Kernel\Middlewares\AuthCookieMiddleware;
// Rota que autentica via cookie (ex: endpoint chamado por página HTML)
$router->get('/api/minha-conta', [Controller::class, 'conta'], [AuthCookieMiddleware::class]);
// O token deve estar no cookie 'auth_token' — definido pelo módulo Auth no login
// Respostas possíveis:
// 401 — cookie ausente, token expirado ou revogado
AuthPageMiddleware
Middleware de autenticação para rotas de página HTML (não API). Quando a identidade está ausente ou inválida, redireciona para /ide/login. A restrição administrativa é responsabilidade do AdminOnlyMiddleware, aplicado separadamente nas páginas de administração.
use Src\Kernel\Middlewares\AuthPageMiddleware;
// Rota de página HTML protegida — redireciona para / se não autenticado
$router->get('/meu-painel', [PainelController::class, 'index'], [AuthPageMiddleware::class]);
// Comportamento:
// Não autenticado → redirect 302 para /
// Autenticado mas não admin_system → redirect 302 para /
// Autenticado como admin_system → passa para o Controller
RouteProtectionMiddleware
Valida assinatura e claims do JWT e permite restringir acesso por papel (role). Tokens de usuário também são consultados na blacklist quando o repositório de revogação está disponível. Token de API puro não aceita restrição por papel; use escopos e o middleware adequado à integração.
use Src\Kernel\Middlewares\RouteProtectionMiddleware;
// Sem restrição de papel — qualquer token válido passa
$router->get('/api/recurso', [Controller::class, 'listar'], [RouteProtectionMiddleware::class]);
// Restrito a um papel específico
$request = $request->withAttribute('roles', ['admin', 'moderador']);
$router->put('/api/recurso/{id}', [Controller::class, 'atualizar'], [RouteProtectionMiddleware::class]);
// Respostas possíveis:
// 401 — token ausente ou inválido
// 403 — papel do usuário não está na lista de roles permitidos
BotBlockerMiddleware
Aplicado globalmente pelo Kernel — você não precisa adicioná-lo nas rotas. Bloqueia automaticamente ferramentas de ataque conhecidas (sqlmap, nikto, nmap, burpsuite, etc.) pelo User-Agent, requisições de API sem User-Agent, e IPs com score de ameaça acumulado alto. Em desenvolvimento local, o bloqueio por score é desativado para não atrapalhar os testes.
sqlmap, nikto, nmap, masscan, nuclei, dirbuster, gobuster, wfuzz, ffuf, feroxbuster, hydra, burpsuite, acunetix, nessus, openvas, scrapy, libwww-perl e outras. O bloqueio gera log em JSON no stderr para integração com Fail2Ban.
HttpsEnforcerMiddleware
Aplicado globalmente pelo Kernel quando COOKIE_SECURE=true no .env. Bloqueia requisições HTTP com 403 e sugere o link HTTPS equivalente. Retorna JSON para chamadas de API e página HTML para browsers.
# Ativa no .env para forçar HTTPS em produção
COOKIE_SECURE=true
COOKIE_HTTPONLY=true
SecurityHeadersMiddleware
Aplicado globalmente pelo Kernel. Garante que todos os headers de segurança estejam presentes em toda resposta: Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, Referrer-Policy e outros. Você não precisa configurar nada — funciona automaticamente.
Combinações recomendadas
| Caso de uso | Middlewares |
|---|---|
| Rota pública com rate limit | [RateLimitMiddleware] |
| Rota pública com proteção de banco | [RateLimitMiddleware, CircuitBreakerMiddleware] |
| Rota privada (qualquer usuário logado) | [AuthHybridMiddleware] |
| Rota privada com rate limit | [RateLimitMiddleware, AuthHybridMiddleware] |
| Rota admin | [AuthHybridMiddleware, AdminOnlyMiddleware] |
| Rota admin com rate limit e circuit breaker | [RateLimitMiddleware, CircuitBreakerMiddleware, AuthHybridMiddleware, AdminOnlyMiddleware] |
| Integração machine-to-machine | [ApiTokenMiddleware] |
| Conteúdo diferente para logados/não logados | [OptionalAuthHybridMiddleware] |
| Página HTML protegida (dashboard) | [AuthPageMiddleware] |
use Src\Kernel\Middlewares\AuthHybridMiddleware;
use Src\Kernel\Middlewares\AdminOnlyMiddleware;
use Src\Kernel\Middlewares\RateLimitMiddleware;
use Src\Kernel\Middlewares\CircuitBreakerMiddleware;
/** @var \Src\Kernel\Contracts\RouterInterface $router */
$admin = [AuthHybridMiddleware::class, AdminOnlyMiddleware::class];
$user = [AuthHybridMiddleware::class];
$dbCircuit = [CircuitBreakerMiddleware::class, ['service' => 'database', 'threshold' => 5, 'cooldown' => 20]];
$pubLimit = [RateLimitMiddleware::class, ['limit' => 30, 'window' => 60, 'key' => 'produto.publico']];
$crtLimit = [RateLimitMiddleware::class, ['limit' => 10, 'window' => 60, 'key' => 'produto.criar']];
// Público com rate limit e circuit breaker
$router->get('/api/produtos', [ProdutoController::class, 'listar'], [$pubLimit, $dbCircuit]);
$router->get('/api/produtos/{id}', [ProdutoController::class, 'buscar'], [$pubLimit, $dbCircuit]);
// Usuário autenticado
$router->get('/api/meus-produtos', [ProdutoController::class, 'meus'], $user);
// Admin com rate limit e circuit breaker
$router->post('/api/admin/produtos', [ProdutoController::class, 'criar'], [$crtLimit, $dbCircuit, ...$admin]);
$router->put('/api/admin/produtos/{id}', [ProdutoController::class, 'atualizar'], [...$admin, $dbCircuit]);
$router->delete('/api/admin/produtos/{id}', [ProdutoController::class, 'deletar'], $admin);