Pular para conteúdo

0018 — Auth das views no micronaut-security nativo

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-06-17 · Decidido em: 2026-06-17

Contexto

O gate de login das views era um AuthFilter (@Filter("/**")) caseiro, rodando em paralelo ao SecurityFilter do micronaut-security — reimplementando autenticação, autorização por path e tradução de rejeição que o framework já dá. A API (/api/**, Bearer estático — decisão 0011) já era nativa. Manter dois mundos de auth aumentava a superfície e divergia do framework.

Decisão

Substituir o AuthFilter por primitivos nativos (passo 1 — mantendo o Firebase client-side, o SessionTokenService e o CsrfTokens desta vez):

  • SessionAuthenticationFetcher (AuthenticationFetcher) lê o cookie xadm_session, valida via SessionTokenService e devolve Authentication.
  • ViewSecurityRule (SecurityRule) replica o tri-estado enabled / bypass (dev/test) / fail-closed (prod) das views.
  • ViewRejectionHandler (ExceptionHandler<AuthorizationException> — o security 5.x removeu o RejectionHandler) faz 302 browser / 401 JSON / 503 fail-closed, preservando o comportamento do filtro antigo.
  • AuthSupport guarda constantes/helpers que sobreviveram; o fetcher mantém os atributos de request, então GlobalViewModel e CsrfTokens ficam intactos.

Escolha entre rotas (Regra nº 1): preferida a migração por peças nativas (mantém Firebase) em vez do full OIDC (Google como provider redirect-based). O OIDC trocaria o mecanismo de login — justamente o que nenhum teste exercita hoje — mudaria UX e exigiria client-secret/redirect URI no Google Console. "Sem quebrar nada" pesou pela rota incremental.

Consequências

  • A autenticação passa a fluir pelo framework (Authentication, @Secured, SecurityRule) — fim do filtro paralelo (~217 linhas).
  • Paridade provada: o AuthIntegrationTest (302/401/403, callback, logout, jornada, Bearer) passou sem alterar asserções. É a rede que protege os próximos passos.
  • Sessão (JWT, SessionTokenService) e CSRF (CsrfTokens) permanecem caseiros por decisão — a migração deles para micronaut-security-jwt/-csrf foi avaliada e descartada: a config nativa é sempre-ligada e exige segredo HS256 válido no startup, enquanto a auth deste app é condicional (segredo vazio em dev/test bypass e em prod mal-configurado). Conciliar isso preservando o fail-closed exigiria mudar a semântica (segredo random-por-boot ou fail-fast no startup) — risco de segurança que não compensa ~150 linhas. O caseiro é limpo, testado e já fica atrás do fetcher nativo (o ganho estrutural veio no passo 1). O CSRF nativo seria multi-aba seguro (stateless HMAC), mas esbarra na mesma impedância do segredo.

Alternativas consideradas

  • Full OIDC (micronaut-security-oauth2): maior redução de código, mas alto risco e mudança de UX/infra; o caminho novo não tem teste. Descartado por ora.
  • Manter o AuthFilter: contraria o objetivo de usar o framework e mantém a divergência com a auth da API. Descartado.