Origem: Relato de degradação de UX/segurança ao manter abas abertas em inatividade (ex: módulo Busca Clínica, painéis administrativos ou páginas públicas). O token CSRF/sessão expira e requisições subsequentes de mutação (POST/PUT/DELETE/AJAX/Fetch/XHR) falham com HTTP 403 ("Token CSRF inválido ou ausente"), exibindo erros crus ou travando a interface.
Lote Vinculado: BATCH-180.
Status: ready-for-intake.
1. Diagnóstico e Causa-Raiz
1.1 Cenário Atual
-
Expiração Silenciosa: O usuário autenticado ou visitante abre uma tela do sistema (como Busca Clínica, painel do Gestor ou formulários) e deixa a aba em segundo plano ou ociosa por determinado período.
-
Falha de Requisição Mutável: Ao retornar e interagir com a tela (disparando requisições AJAX,
fetchouXMLHttpRequestmutáveis como POST, PUT ou DELETE), o backend valida o token CSRF emseguranca_csrf_requisicao_validar(). -
Resposta 403 Rígida: O backend identifica a discrepância ou ausência do token e executa
gestor_csrf_resposta_invalida()(emgestor/gestor.php), devolvendo HTTP 403 com JSON{"status":"error","message":"Token CSRF inválido ou ausente."}. -
Degradação de Experiência do Usuário (UX):
-
A camada de frontend atual (
gestor/assets/global/global.js) anexa o token atual no envio (X-CSRF-Tokene_csrf_token), mas não possui lógica de interceptação de 403 CSRF, renovação sob demanda (Silent Refresh) nem fila de repetição transparente (Retry). -
O usuário recebe mensagens técnicas confusas ("Erro 403", "Acesso Negado") ou tem seu fluxo interrompido com perda dos dados preenchidos no formulário/busca, mesmo quando a sessão do usuário ainda está viva no servidor ou pode ser recuperada.
-
2. Escopo Normativo da Solução
2.1 Backend: Endpoint de Token CSRF e Identificador de Erro Padronizado
-
Endpoint de Consulta/Renovação de CSRF:
-
Expor uma rota leve de sistema no roteador central (
gestor/gestor.php), por exemplo_gestor-csrf-token(ou_gestor-csrf-refresh). -
A rota deve responder JSON estruturado com o token CSRF ativo da sessão (
gestor_csrf_token()) e o status da sessão:{ "status": "success", "token": "...", "authenticated": true } -
Em caso de sessão destruída/inválida para áreas que exigem autenticação obrigatória, responder com código de erro correspondente (
AUTH_EXPIREDouSESSION_EXPIRED) e cabeçalhoX-Gestor-Auth-Redirectpara login gracioso. -
A rota
_gestor-csrf-tokendeve ser isenta de validação prévia de CSRF emseguranca_csrf_requisicao_validar()para leitura.
-
-
Identificador de Máquina no Erro 403 (
gestor_csrf_resposta_invalida):-
Atualizar a resposta JSON de
gestor_csrf_resposta_invalida()emgestor/gestor.phppara incluir um código de erro padronizado:{ "status": "error", "code": "CSRF_INVALID_OR_EXPIRED", "message": "Token CSRF inválido ou ausente." } -
Isso permite que interceptores de frontend diferenciem de forma determinística um erro de CSRF expirado de outros erros de permissão ou bloqueio (como 403 de perfil/ACL).
-
2.2 Frontend (global.js): Silent Refresh & Retry Queue
-
Fila de Requisições e Mecanismo de Renovação Concorrente:
-
Em
gestor/assets/global/global.js, implementar um gerenciador de renovação de token (renovarCsrf()):-
Ao detectar uma falha HTTP 403 com
code === 'CSRF_INVALID_OR_EXPIRED'(ou mensagem de CSRF) em chamadas de$.ajax,window.fetchouXMLHttpRequest: -
Se uma renovação já estiver em andamento, enfileirar a requisição para retry automático.
-
Se não estiver em andamento, disparar a requisição de renovação para
_gestor-csrf-token. -
Ao receber o novo token:
-
Atualizar
window.gestor.csrfTokene a tag<meta name="csrf-token" content="...">. -
Se executando em iframe (
srcdocou child frame), propagar parawindow.parent.gestor.csrfTokenquando aplicável. -
Re-executar (retry) todas as requisições enfileiradas atualizando seus cabeçalhos
X-CSRF-Tokene parâmetros_csrf_token. -
Se a renovação falhar (sessão encerrada/401/403 irrecuperável):
-
Rejeitar as requisições enfileiradas e disparar a recuperação graciosa de sessão.
-
-
-
Detecção Proativa de Foco / Retorno à Aba (
visibilitychange):-
Registrar um listener de
visibilitychangenodocumentcom controle de frequência (throttling/debounce de ~30 segundos):-
Quando
document.visibilityState === 'visible', se a aba esteve em segundo plano por tempo significativo, efetuar uma verificação/atualização silenciosa do token CSRF em background. -
Assim, quando o usuário clicar em qualquer ação após horas de inatividade, o token já estará atualizado antes mesmo do primeiro clique.
-
-
-
Recuperação Graciosa em Caso de Sessão Encerrada:
-
Se a renovação constatar que a sessão de login expirou definitivamente:
-
Exibir modal amigável de sessão expirada ou efetuar redirecionamento transparente para
/signin/preservando a URL de retorno (redirecionar-local/ query string), evitando travamento em branco ou alert técnico.
-
-
3. Critérios de Aceite e Validação
-
Endpoint
_gestor-csrf-tokenFuncional:-
Requisições GET/AJAX para
_gestor-csrf-tokenretornam HTTP 200 com JSON{ status: 'success', token: '...' }.
-
-
Replay Transparente de Requisições AJAX/Fetch/XHR:
-
Requisições POST que receberem 403 por token expirado realizam a renovação automática e completam a operação com sucesso sem que o usuário perceba o erro.
-
-
Tratamento de Múltiplas Requisições Simultâneas:
-
Múltiplas requisições concorrentes falhando por CSRF disparam apenas UMA chamada de renovação e todas são retentadas após a obtenção do novo token.
-
-
Isolamento de Erros de Permissão Genuínos:
-
Erros 403 por falta de permissão de acesso (ACL) ou acesso restrito continuam sendo tratados como negações legítimas e não entram em loop de retry.
-
-
Testes Automatizados:
-
Testes unitários no PHPUnit (
tests/Unit/PHP/) cobrindo o endpoint de CSRF e código padronizado de erro. -
Testes no Vitest (
tests/Unit/JS/) cobrindo a fila de retry, silent refresh, atualização de meta tag evisibilitychange. -
Suítes de testes 100% verdes com exit 0.
-