PHP MVCDOCUMENTAÇÃO
v2.0.0

Middleware e proteção

Middlewares Web/API presentes, ordem de execução e perfis de rate limit.

Contrato

O Router recebe middlewares por rota/grupo e chama handle(Request $request, ...$args). Cadastre nomes das classes corretamente e inclua o arquivo/classe no autoload da aplicação. A cadeia executa em ordem antes do controller; middlewares podem responder e encerrar a requisição.

Implementações existentes

  • AuthMiddleware exige sessão autenticada.
  • GuestMiddleware restringe rotas a visitantes.
  • RoleMiddleware:$role aplica verificação de role.
  • CsrfMiddleware protege mutações Web.
  • RateLimitMiddleware:$profile usa arquivos JSON com lock.
  • SecurityHeadersMiddleware adiciona cabeçalhos de segurança.
  • DevelopmentMiddleware existe para acesso de desenvolvimento.
  • ApiAuthMiddleware exige token Bearer válido.
  • CorsMiddleware aplica política CORS da API e responde preflight.

Perfis de rate limit

  • login: 5 tentativas/15 min por IP+email.
  • register: 5/60 min por IP.
  • forgot: 3/60 min por IP.
  • api: 60/min por IP.
  • default: 30/min por IP.

Login/registro/esqueci senha falham fechado em falha de escrita; API/default preservam disponibilidade. Resposta limitada é HTTP 429 com Retry-After.

CORS

CORS_ALLOWED_ORIGINS recebe origens separadas por vírgula. Wildcard só é aceito fora de produção; métodos/headers permitidos estão em config/api.php. OPTIONS termina com 204 no middleware.

$router->group([
    'prefix' => '/api/v1',
    'middleware' => ['CorsMiddleware', 'RateLimitMiddleware:api'],
], function (Router $r): void {
    $r->get('/users', [UserApiController::class, 'index'], ['ApiAuthMiddleware']);
});

Pipeline e ordem de aplicação

Router reúne primeiro os middlewares herdados dos grupos e depois os específicos da rota; executa a lista antes da action. Use middleware para decisões transversais (autenticação, CSRF, CORS, limite de taxa), não para renderizar a View de um recurso.

$router->group([
    'prefix' => '/api/v1',
    'middleware' => ['CorsMiddleware', 'RateLimitMiddleware:api'],
], function (Router $r): void {
    $r->get('/users', [UserApiController::class, 'index'], ['ApiAuthMiddleware']);
});

Esta estrutura corresponde ao grupo real de routes/api.php: CORS e rate limit cercam todos os endpoints; ApiAuthMiddleware é específico das rotas privadas. Na Web, /dashboard aplica AuthMiddleware ao grupo e CSRF fica nas actions POST.

Perfis de rate limit e decisão operacional

PerfilLimite/janelaChave
login5 por 15 minIP + e-mail normalizado
register5 por 60 minIP
forgot3 por 60 minIP
api60 por 1 minIP
default30 por 1 minIP

O limitador grava arquivos em storage/cache/ratelimit. Se não conseguir registrar tentativas, login/register/forgot falham fechado; api/default preservam disponibilidade. Uma resposta bloqueada usa HTTP 429 e Retry-After. Esse armazenamento local não é um contador distribuído para vários nós.

CorsMiddleware lê CORS_ALLOWED_ORIGINS; origens autorizadas devem ser explícitas em produção. OPTIONS pode terminar no middleware com 204. TRUSTED_PROXIES também afeta identificação do IP: não confie em X-Forwarded-* vindo diretamente do cliente. Veja autenticação e CSRF.

Rate limit: chave, armazenamento e respostas

Os perfis são configurados em RateLimitMiddleware::$profiles; cada requisição permitida também incrementa o contador sob lock exclusivo flock(). O arquivo fica em storage/cache/ratelimit/. É um armazenamento local por instância de aplicação, não um contador distribuído entre vários nós.

PerfilLimite/janelaChaveFalha ao gravar
login5 / 900 sIP + e-mail lido de $_POST['email']Fail closed: responde 429 com espera de 60 s.
register5 / 3600 sIPFail closed.
forgot3 / 3600 sIPFail closed.
api60 / 60 sIPFail open: registra falha e permite a requisição.
default30 / 60 sIPFail open: registra falha e permite a requisição.

O bloqueio acontece quando attempts > max, então o valor máximo ainda é admitido; a resposta usa HTTP 429 e header Retry-After. Requisições AJAX/JSON recebem JSON com success=false, message e retry_after; navegador recebe flash e redirect seguro.

Nuance do perfil login: a chave lê o e-mail de $_POST, não do corpo JSON decodificado pelo FormRequest. Em login JSON, esse componente pode ficar vazio; nessa entrada o perfil se comporta na prática como limite por IP. Não documente o perfil como limite por IP+e-mail para payload JSON.

Quando o limitador identifica IP por cabeçalhos encaminhados, configure TRUSTED_PROXIES só com proxies controlados; não confie em X-Forwarded-For enviado diretamente pelo cliente. Consulte Configuração e Autenticação.

Contratos dos middlewares de autenticação e ambiente

Os middlewares recebem handle(Request $request): void; podem encerrar a resposta, redirecionar ou deixar a execução prosseguir. AuthMiddleware guarda autenticação e direciona o navegador para auth/login quando não autorizado. RoleMiddleware espera que a autenticação já tenha sido checada antes (inclua AuthMiddleware primeiro); a role padrão do middleware é admin. Falhas de autorização podem responder 403 em navegador e JSON em requisição API conforme o header de aceitação; não confunda isso com permissões por objeto/ACL.

DevelopmentMiddleware::handle(Request $request): void só permite a rota quando APP_ENV é literalmente local; em qualquer outro ambiente responde 404. Isso é uma trava por ambiente, não autorização por usuário ou IP; não a use como proteção de dado nem suponha disponibilidade em staging.

Redirects relativos no código atual: AuthMiddleware e RoleMiddleware chamam redirect('auth/login')/redirect('dashboard') sem barra inicial. O helper pode substituir esses destinos por APP_URL ou /; não prometa que o browser chegará às rotas nomeadas. O caminho de falha de role de navegador define 403 antes do redirect; para API/AJAX o middleware emite JSON 403 e encerra.

SecurityHeadersMiddleware é global

Além de middleware por rota, SecurityHeadersMiddleware é registrado globalmente no bootstrap. O código define CSP com nonce e diretivas; Permissions-Policy; remove X-Powered-By; e adiciona HSTS somente em produção via HTTPS. Em scripts inline, use cspNonce() para fornecer o nonce compatível; não enfraqueça a CSP para contornar erro de template.