0034 — Refresh do JWT por token e teto absoluto de sessão¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-15 · Decidido em: 2026-09-15
Contexto¶
Teste de papel e inativação no bi-comercial com usuário pAbast, em 2026-09-15: o usuário inativado seguiu com acesso, e a troca de papel (LUBRIFICANTE → COMERCIAL) não apareceu nem com F5. As duas coisas só refletiam com logout/login.
A causa foi medida no código. A sessão pAbast guarda a senha só em memória, e o app renovava o JWT
re-logando em /api/auth/pabast/login. Depois de um F5 a senha some e não há como renovar: o papel fica
congelado até o JWT vencer (AUTH_TOKEN_EXPIRATION_MINUTES, 24 h). O central não tem revogação no
servidor, e o PowerSync aceita o token até o exp.
A casa fixou o desenho na ADR central 0037 e no bullet de sessão da norma de Segurança. O prazo de revogação é o TTL do access token, de no máximo 60 min. O app renova trocando o token vigente, e a sessão tem teto absoluto de no máximo 7 dias. O contrato (rota, códigos, claims) é do central, e esta decisão o registra.
Decisão¶
POST /api/auth/refreshtroca um JWT de app-user ainda válido por um novo, com a mesma decisão do login (cliente ativo, método de auth, acesso e papel atuais) e sem re-provar a credencial. Só oAuthorization: Bearer, sem corpo; cliente e app saem do próprio token. Token semoauth_app_id(legadoscope=full, anônimo,xadm_admin) não tem via de refresh:403 not_app_user.- Claims novos nos tokens de app-user:
sub_type(pabast|oauth) diz quem é osub, eauth_time(epoch s) guarda o último login com credencial. O refresh copia oauth_time. - Token de antes do deploy, sem
sub_type, é classificado peloemail. Medido no código: o login pAbast emite sememaile os dois caminhos oauth sempre o emitem (vazio, se o Firebase não o deu). Semauth_time, a sessão conta doiat. - Teto absoluto de sessão:
AUTH_SESSION_MAX_HOURS, default 168 h (7 dias). É também o máximo da norma, e valor acima é cortado, como o TTL já é cortado em 1440 min. O teto conta doauth_time. Além dele, o refresh recusa com403 session_expired, e oexpemitido nunca passa do teto:exp = min(agora + TTL, auth_time + teto). Sem o corte, um refresh na hora 167 daria um token válido até a hora 191. - pAbast: o
subprecisa existir nopabast_senhasdo cliente, comparado comTRIM(o ERP entrega oidcom espaço na borda); senão,403 access_revoked. Sem acessoATIVO, o token sai semrole, em200, como no login — refresh e login nunca discordam, e reativar reflete sem logout. - oauth: só
ATIVOrenova;PENDENTE,REJEITADO,INATIVOou sem linha dão403 access_revoked. A conta@xadmsegueADMINimplícito, sem linha de acesso. - Cliente apagado revoga como o desativado:
403 cliente_inactive. O login devolve400 cliente_not_found, que o app não trata como revogação e o manteria logado até oexp. - Os
codede 403 são contrato com o app, que decide deslogar por eles (_codigosRevogacaonoAppConnectordo bi-comercial). O contrato vive no contrato da API, não na norma; código novo de revogação entra lá e no app.
Consequências¶
- O refresh não revoga o token anterior: o PowerSync e toda API o aceitam até o
exp. O prazo de revogação no servidor é o TTL, que a norma limita a 60 min. - Pendente, na ordem da ADR central 0037: o
AUTH_TOKEN_EXPIRATION_MINUTESde produção segue em 1440 até os apps que renovam sessão adotarem o refresh. Baixá-lo antes faria cada app sem refresh re-logar a cada TTL. No mesmo passo, o clamp de 1440 doCentralSettingscai para 60. - Como o refresh não re-prova a senha, senha trocada no ERP não derruba a sessão antes do teto. É o preço do refresh, e o teto é o limite dele.
- O rate limit é o dos logins (60 req/min por IP). Com uma chamada a cada 5 min por sessão, cabem cerca de
300 sessões atrás do mesmo IP de saída antes do
429, sem contar os eventos de foco. Com o TTL curto, o app também renova antes doexp. - O
expires_inpode vir menor que o TTL perto do teto, e o app precisa usar o valor devolvido. - Tokens emitidos antes do deploy continuam renováveis (classificação pelo
email, sessão peloiat) e somem sozinhos em até 24 h.
Alternativas consideradas¶
- Classificar o token legado por consulta ao
app_user_access(sugestão do handoff): descartada. A conta@xadmnão tem linha de acesso, cairia como pAbast e tomariaaccess_revoked. - Refresh token opaco guardado no banco: descartado na ADR central 0037. Custa tabela, rotação e detecção de reuso, e o refresh token no browser é tão exfiltrável quanto o access.
- Teto só na recusa, sem cortar o
exp: descartada, porque o último token viveria até um TTL além do teto. 403para pAbast sem papel: descartada. O refresh discordaria do login, e reativar o acesso exigiria novo login.