Problemas de auth JWT em protótipos: expiração, refresh, desvio de relógio
Problemas de autenticação JWT em protótipos parecem aleatórios. Aprenda correções para expiração, rotação de refresh, desvio de relógio e padrões seguros de armazenamento de tokens.

Por que tokens JWT param de funcionar aleatoriamente em protótipos
A maioria dos problemas de autenticação com JWT em protótipos aparece do mesmo jeito: um login funciona, depois usuários de repente recebem erros 401, são mandados de volta para a tela de entrada ou veem um app que só funciona após um refresh.
Parece aleatório porque JWTs se baseiam em tempo, seu protótipo normalmente é composto por várias partes móveis, e pequenas diferenças se acumulam. O relógio de um laptop está alguns minutos adiantado. Um servidor está em outro fuso ou com drift. Uma segunda aba do navegador mantém um token antigo e sobrescreve o mais novo. De repente a mesma requisição funciona para você e falha para outra pessoa.
Um modelo mental rápido ajuda: quase toda falha de auth cai em um destes três grupos.
- Tempo: expiração (
exp), emitido em (iat), desvio de relógio, tokens de acesso de curta duração - Armazenamento: token ausente, sobrescrito, apagado ou preso no lugar errado
- Validação: chave secreta/chave pública errada,
aud/isserrados, token revogado ou rotacionado
Antes de mudar código, capture o básico. Isso economiza horas de tentativa e erro.
- A resposta exata que falhou (status + mensagem) e em qual endpoint ocorreu
- Um timestamp do cliente e dos logs do servidor para a mesma requisição
- O horário e fuso do dispositivo (especialmente em mobile)
- As claims do payload do token (
exp,iat,iss,aud) e quando ele foi gerado
Exemplo concreto: um fundador testa no Mac e tudo funciona. Um usuário no Windows fica desconectado a cada 10–15 minutos. O problema real não é “desconexões aleatórias” — o relógio do usuário está 6 minutos atrasado, o access token expira rápido e o servidor o rejeita sem tolerância. Abra uma segunda aba e você também pode ter uma aba atualizando enquanto a outra sobrescreve tokens armazenados.
Se seu protótipo foi gerado por ferramentas como Lovable, Bolt ou Cursor, é comum ver tratamento inconsistente de tokens entre páginas. FixMyMess frequentemente encontra uma mistura de expirações curtas, lógica de refresh ausente e armazenamento inseguro ao mesmo tempo.
Conceitos básicos de JWT que você precisa para debug (sem o dump teórico)
Um JWT é apenas uma nota assinada. Seu servidor a assina para depois verificar que o token foi emitido por você e não foi alterado. A maioria dos JWTs não é criptografada, então qualquer um com o token pode decodificá-lo e ler o que está dentro.
Isso leva à primeira regra prática: trate JWTs como carteiras de identidade legíveis, não como cofres secretos. Se um token vaza (logs, armazenamento do navegador, screenshots, ferramentas de analytics), o que estiver dentro também vazou.
As poucas claims que explicam a maioria das falhas
Quando tokens “param de funcionar aleatoriamente”, normalmente é um destes campos, ou como seu app os checa:
exp(expires at): o momento em que o token deve ser rejeitado.iat(issued at): quando o token foi criado. Frequentemente usado para depurar problemas de tempo.nbf(not before): o token deve ser rejeitado até esse horário.aud(audience): para quem o token se destina (uma API específica).iss(issuer): qual sistema emitiu o token.sub(subject): o usuário ou entidade que o token representa.
Um desacordo comum em protótipos: o frontend envia um token para a API B que foi emitido para a API A ( aud errado), ou o backend é rigoroso sobre iss em um ambiente e não em outro.
“Falha” pode significar duas coisas opostas
Problemas de auth com JWT muitas vezes surgem de checagens ausentes ou verificações excessivamente rígidas.
Se as checagens faltam, tokens podem funcionar quando não deveriam (buraco de segurança), e então “de repente falham” depois que você adiciona uma regra de validação.
Se as checagens são rígidas demais, usuários são desconectados por diferenças mínimas, como um servidor com relógio 60 segundos adiantado (mais sobre desvio de relógio abaixo), ou um token válido que não corresponda exatamente à string de aud/iss.
O que nunca pertence em um JWT
Não coloque segredos ou dados sensíveis no payload de um JWT: senhas, chaves de API, credenciais de banco de dados, códigos de reset ou dados pessoais que você não gostaria que fossem copiados para uma planilha. Mantenha-o mínimo: um ID (sub), talvez uma role ou algumas permissões e as claims de tempo.
Se você está herdando um protótipo gerado por IA, isso é um problema frequente que vemos na FixMyMess: tokens que acidentalmente incluem chaves internas ou dados de usuário demais, e então são armazenados em lugares inseguros. Mesmo que a autenticação “funcione”, está a um vazamento de se tornar um incidente real.
Problemas de expiração: configurações exp e iat que causam logouts-surpresa
A maioria dos problemas de JWT em protótipos não é “aleatória”. Normalmente é matemática de expiração que parece OK em testes locais e depois quebra quando dispositivos reais, redes reais e diferenças de tempo aparecem.
Alguns erros causam a maioria dos logouts-surpresa:
expmuito curto. Cinco minutos parecem seguros, mas são brutais para protótipos com cold starts lentos, apps móveis em segundo plano e conexões instáveis. Usuários voltam após uma pausa curta e todas as chamadas falham.expausente. Algumas bibliotecas aceitam tokens sem expiração, outras não, e seu próprio código pode tratá-los como expirados. Isso cria comportamento confuso e inconsistente entre ambientes.iatno futuro. Isso acontece quando o relógio do servidor está errado, você usa o fuso horário errado ou gera tokens em um serviço e valida em outro. Muitos validadores rejeitam tokens “ainda não válidos”.
Um padrão prático padrão é: token de acesso de curta duração + refresh token de longa duração. Mantenha o access token curto o suficiente para limitar o dano se for roubado, mas longo o bastante para sobreviver ao uso normal. Para muitos protótipos, um bom ponto de partida é 10 a 20 minutos para o access token, e dias para o refresh. Depois você pode apertar isso.
O comportamento do cliente importa tanto quanto as configurações do token. Uma boa regra: trate um 401 uma vez.
- Se uma requisição retorna 401, chame o refresh.
- Se o refresh for bem-sucedido, tente novamente a requisição original uma vez.
- Se a nova tentativa falhar ou o refresh falhar, desconecte e mostre uma mensagem clara.
Isso evita loops infinitos de retry e telas que “não param de carregar”.
No servidor, evite erros 500 vagos quando um token expirou. Retorne um 401 claro para access tokens expirados ou inválidos, e um 401 (ou 403, se preferir) quando o refresh token for inválido ou revogado. Isso deixa óbvio se o problema é expiração ou uma falha real de backend.
Exemplo: um fundador testa no laptop e tudo funciona. Usuários em mobile abrem o app depois do almoço, o access token expirou e o app continua chamando a API com o token antigo até desistir. Com o padrão de refresh-and-retry uma vez, esse mesmo usuário continua sem perceber.
Se você herdou um fluxo de auth gerado por IA onde tokens “às vezes” falham, a FixMyMess frequentemente encontra uma mistura de exp muito curto, validação inconsistente e lógica de refresh ausente na primeira auditoria.
Desvio de relógio (clock skew): quando tokens corretos falham porque o tempo está errado
Alguns problemas de JWT não são causados por código ruim ou “tokens inválidos”. O token pode estar correto, assinado e sem alterações, e ainda assim falhar porque o relógio do dispositivo ou do servidor está errado.
Desvio de relógio acontece mais em protótipos do que se imagina: um telefone com hora manual, uma VM que deriva, um container rodando com fonte de tempo mal configurada, ou dois servidores no mesmo app que discordam por um minuto.
Quando o tempo está errado, normalmente você vê falhas em torno de claims baseadas em tempo:
nbf(not before): o servidor acha que o token ainda não está ativoexp(expires): o servidor acha que o token já expirouiat(issued at): algumas bibliotecas usam isso para checagens extras e podem rejeitar tokens “futuros”
Um cenário comum: você assina um token no Servidor A, mas a requisição do usuário é validada no Servidor B cujo relógio está 45 segundos adiantado. De repente, usuários são desconectados “aleatoriamente”, ou o login funciona e depois falha na próxima navegação.
Correção prática: adicione uma pequena tolerância (leeway) na validação
A maioria das bibliotecas JWT permite uma pequena margem de tolerância ao checar exp e nbf. Um bom ponto de partida é 30 a 120 segundos. Mantenha pequeno: leeway serve para lidar com drift, não para estender sessões.
Se você usar leeway, trate-o como um guarda-vento, não um band-aid. Se precisar de 10 minutos de tolerância, provavelmente há um problema de sincronização de tempo ou implantação.
Correção operacional: torne o tempo consistente em todos os lugares
Leeway reduz falhas falsas, mas você ainda deve corrigir a causa raiz. Verificações rápidas que pegam a maioria dos problemas:
- Garanta que todos os servidores e agentes de build sincronizem com a mesma fonte de tempo (NTP)
- Evite misturar hosts com tempo correto e containers com tempo isolado ou mal configurado
- Verifique se dispositivos móveis de teste estão com hora automática ativada
- Em setups com múltiplos servidores, confirme que requisições não estão saltando entre nós com tempos diferentes
Se você está herdando um protótipo gerado por IA (comum em Lovable, Bolt, v0, Cursor ou Replit), bugs de desvio de relógio podem ser mascarados pelo “funciona na minha máquina”. Quando a FixMyMess audita falhas de auth, frequentemente encontramos que uma pequena tolerância junto com sincronização de tempo adequada remove os logouts instáveis sem mudar todo o design de auth.
Refresh tokens: a peça que falta na maioria dos fluxos de autenticação de protótipos
A maioria dos problemas de JWT em protótipos ocorre porque o app tenta usar um token só para tudo: um access token de curta duração que também tem que manter o usuário conectado por dias. Essa tensão é o motivo das sessões parecerem aleatórias. Um refresh token existe para manter a sessão viva sem tornar o access token de longa duração (o que é arriscado se vazar).
Access tokens devem ser chatos: expiração curta, enviados com frequência, fáceis de substituir. Refresh tokens devem ser raros: usados apenas para obter um novo access token e mantidos fora de locais onde JavaScript ou logs possam pegá-los facilmente.
Um erro comum em protótipos é armazenar o refresh token da mesma forma que o access token (por exemplo, em localStorage) e tratá-lo como credencial normal de API. Quando esse token vaza, um atacante pode gerar novos access tokens até você notar. Outro erro é não ter refresh token algum, então o app “resolve” desconexões definindo expiração do access token muito longa. Isso normalmente vira dívida técnica de segurança depois.
Antes de construir o fluxo, decida o que “sessão” deve significar para seu produto:
- Quanto tempo um usuário deve permanecer conectado sem abrir o app?
- Fechar o navegador deve desconectar ou não?
- Você quer uma sessão por dispositivo, ou entrar em um dispositivo deve desconectar outro?
- O que acontece após uma mudança de senha: desconectar em todos os lugares ou apenas nas sessões novas?
O uso real também adiciona casos de borda que protótipos raramente tratam. Usuários abrem múltiplas abas, redes caem no meio da requisição e duas requisições podem tentar fazer refresh ao mesmo tempo. Se você não controlar isso, aparecem loops (refresh, falha, retry) ou 401s repentinos que parecem “problemas JWT” mesmo quando seus tokens estão OK.
Se você herdou um protótipo gerado por IA (Lovable, Bolt, v0, Cursor, Replit), essa camada de refresh frequentemente está ausente ou meio implementada. Corrigi-la normalmente remove os bugs de “funcionou ontem” primeiro.
Passo a passo: um fluxo de refresh que funciona com rotação
A maioria dos problemas de JWT aparece quando access tokens expiram e o app não tem um jeito confiável de recuperar. Um fluxo de refresh baseado em rotação corrige isso ao tornar a expiração algo normal, não assustador.
O fluxo (login → refresh → retry)
Comece emitindo dois tokens no login: um access token de curta duração (minutos) e um refresh token de longa duração (dias ou semanas). O access token é o que sua API verifica em cada requisição. O refresh token é usado apenas para obter um novo access token.
Uma sequência simples e confiável:
- O login retorna access token + refresh token
- O cliente chama APIs com o access token até falhar com 401
- O cliente chama o endpoint de refresh com o refresh token
- O servidor retorna um novo access token e um novo refresh token
- O cliente tenta novamente a chamada API original uma vez
A rotação é o detalhe chave: a cada refresh você gera um refresh token novo, e o antigo se torna inválido. Assim, se um refresh token vaza, ele para de funcionar assim que o usuário legítimo fizer um refresh.
Regras de rotação que você deve implementar no servidor
Para tornar a rotação realmente segura (e não quebrar usuários aleatoriamente), mantenha estas regras rigorosas:
- Armazene refresh tokens no servidor (hash), com id do usuário, expiração e um id único de token.
- No refresh, aceite apenas o id de token corrente, então marque-o imediatamente como usado/revogado e emita um novo id de token.
- Se um token antigo for apresentado novamente, trate como suspeito e revogue toda a sessão (ou exija novo login).
Concorrência é onde protótipos ficam instáveis. Duas abas, um duplo clique ou um retry do app pode disparar dois refreshes ao mesmo tempo. Uma estratégia de graça pequena evita logouts-surpresa: permita que o refresh token previamente válido seja usado mais uma vez por uma janela curta (por exemplo 10–30 segundos) se ele acabou de ser rotacionado, mas só se você conseguir detectar que pertence à mesma cadeia de sessão.
Se você está lutando contra problemas JWT que parecem aleatórios, geralmente é porque rotação, armazenamento ou concorrência estão meio implementados. Times frequentemente trazem à FixMyMess um protótipo de Lovable/Bolt/v0/Cursor/Replit onde o refresh parece “pronto” mas quebra com uso real, e nós endurecemos isso para um fluxo seguro de produção rapidamente.
Padrões seguros de armazenamento de tokens (web e mobile)
Se você vê problemas de JWT que só aparecem fora do seu próprio navegador, o armazenamento costuma ser a razão. Um protótipo pode parecer OK em testes e começar a desconectar pessoas aleatoriamente quando páginas reais, scripts de terceiros ou diferentes dispositivos entram em cena.
Web: trate o refresh token como uma senha
Para apps web, o padrão mais seguro é armazenar o refresh token em um cookie httpOnly e Secure. httpOnly impede que JavaScript o leia (ajuda muito se você tiver um bug de XSS). Secure garante que só viaje por HTTPS.
Flags do cookie não são "definir e esquecer". Faça-as uma escolha deliberada:
- httpOnly + Secure: base recomendada para refresh tokens.
- SameSite=Lax: geralmente funciona para navegação normal e reduz risco de CSRF.
- SameSite=None: necessário para setups cross-site (domínios diferentes), mas exige Secure e aumenta exposição a CSRF.
- Proteção CSRF: se o endpoint de refresh for baseado em cookie, adicione defesas CSRF (por exemplo, um cabeçalho de token CSRF ou double-submit token).
Evite armazenar tokens de longa duração em localStorage ou sessionStorage. É conveniente, mas se qualquer script na página puder rodar (XSS, dependência comprometida, extensão maliciosa), ele pode ler tokens e enviá-los para fora.
Access tokens são diferentes: mantenha-os em memória quando possível e de curta duração. Se um refresh de página os perder, tudo bem — o cookie de refresh pode mintar um novo.
Mobile: use armazenamento seguro, mantenha access tokens curtos
No iOS e Android, guarde refresh tokens no armazenamento seguro da plataforma (Keychain no iOS, Keystore no Android). Não coloque refresh tokens em armazenamento simples do app ou logs.
Um padrão prático:
- Refresh token: armazenamento seguro, longa duração, rotacionado.
- Access token: apenas em memória, expiração curta, substituído com frequência.
- Ao enviar o app para background/fechar: descarte o access token e busque um novo no resume.
Uma fonte silenciosa de falhas “aleatórias”: tokens vazando para lugares inesperados. Nunca coloque access tokens em URLs (query params) e os remova de logs, eventos de analytics, relatórios de crash e popups de erro. Por exemplo, se seu app registra a requisição inteira quando uma chamada falha, pode capturar o cabeçalho Authorization.
Se você herdou um fluxo gerado por IA que mistura cookies, localStorage e access tokens de longa duração, a FixMyMess pode auditar rapidamente e apontar os pontos exatos de vazamento e escolhas inseguras antes do lançamento.
Armadilhas comuns de protótipos que tornam a autenticação instável
Bugs de JWT em protótipos frequentemente parecem aleatórios porque o sistema está meio rigoroso e meio frouxo. “Funciona na minha máquina”, depois quebra após um redeploy, a adição de um serviço ou quando um usuário abre outra aba.
Armadilha 1: pular checagens de iss e aud
Muitos protótipos só validam assinatura e expiração. Isso pode ser suficiente para um backend único, mas fica frágil quando você adiciona uma segunda API, um worker em background ou um serviço admin separado.
Se você não validar iss e aud, pode acabar aceitando tokens destinados a outro serviço, e depois “apertar a segurança” faz com que usuários reais recebam 401s porque tokens existentes não batem com as novas regras.
Uma forma simples de evitar surpresas é decidir cedo:
- Uma string
issuerpara seu serviço de auth - Uma audience por API (ou uma audience compartilhada se realmente houver uma única API)
- Configurações consistentes entre dev, staging e prod
Armadilha 2: segredos mudando entre ambientes ou redeploys
Protótipos frequentemente geram segredos na inicialização, usam .env diferentes por máquina ou rotacionam chaves acidentalmente ao deployar. O resultado parece “tokens param de funcionar aleatoriamente”, mas o problema real é que o servidor não consegue mais verificar tokens que emitiu antes.
Se você precisa redeployar com frequência, trate chaves de assinatura como senha de banco: mantenha estáveis e gerenciadas. Se planeja rotacionar chaves, faça-o deliberadamente (por exemplo, suportando a chave atual e a anterior por um curto overlap).
Armadilha 3: confiar na decodificação no cliente em vez da verificação no servidor
Decodificar um JWT no navegador (ou app) não é o mesmo que verificá-lo. Um protótipo pode ler o payload, assumir que o usuário está logado e pular uma checagem real no servidor até mais tarde.
Isso cria estados confusos: a UI diz “logado”, mas a API rejeita requisições. Faça o servidor ser a fonte da verdade e trate “API retorna 401” como sinal real para refresh ou re-autenticação.
Armadilha 4: cachear o estado de auth incorretamente (especialmente entre abas)
Um bug comum: uma aba atualiza tokens e outra continua usando um access token antigo, e seu app alterna entre funcionar e falhar. Outra versão: você cacheia o “usuário atual” em memória e nunca o atualiza após um refresh.
Se seu app roda em múltiplas abas, decida como atualizações de estado de auth se propagam. Pelo menos, trate eventos de “token atualizado” e “desconectado” de forma clara para não continuar enviando tokens obsoletos.
Armadilha 5: enviar atalhos de debug de auth
Código de protótipo às vezes aceita tokens não assinados, usa o algoritmo none ou ignora verificação para testes. Se isso chegar à produção, você tem risco de segurança e falhas estranhas quando partes do sistema discordarem sobre o que é válido.
Se você herdou um protótipo gerado por IA e a autenticação parece instável, a FixMyMess pode auditar rapidamente a base (incluindo verificação de token, lógica de rotação e padrões de armazenamento) e apontar exatamente quais armadilhas estão acontecendo antes que usuários as encontrem.
Exemplo: funciona para você, mas usuários são desconectados com frequência
Uma história comum: você testa o app o dia todo no seu laptop e a autenticação parece ok. Você lança um preview para usuários reais e de repente recebe mensagens como “sou desconectado a cada 10 minutos” ou “o login funciona uma vez e depois para aleatoriamente”.
Cenário realista. Um fundador monta um protótipo em uma ferramenta de IA, roda localmente e faz login na mesma aba do navegador. Em produção, usuários abrem no celular, mudam de rede, colocam o app em segundo plano e voltam depois. É exatamente aí que problemas JWT aparecem: expirações curtas, diferenças de relógio e lógica de refresh que só funciona no caminho feliz.
Como reproduzir sem chutar no escuro
Comece transformando “desconexões aleatórias” em uma linha do tempo. Peça a um usuário afetado o horário em que fez login e o horário em que foi desconectado (o fuso dele importa). Depois colete uma requisição que falhou no servidor (ou nos logs) e capture:
- Hora do servidor quando a requisição foi rejeitada (401)
- Os valores
expeiatdo token (decodificados no servidor) - Se houve tentativa de refresh e se falhou
- Se o usuário tinha múltiplos dispositivos ou abas abertas
Se possível, compare o relógio do servidor com uma fonte confiável e com sua máquina local. Alguns minutos de drift já quebram validação estrita.
Causas mais prováveis
Em protótipos, estes problemas reaparecem:
expmuito curto (5–15 minutos) e fluxo de refresh não confiável, então usuários sofrem logouts-surpresa.- Falhas por desvio de relógio: o backend checa
expeiatsem tolerância e uma máquina está com tempo errado. - Bugs na rotação do refresh token: você rotaciona tokens, mas não trata reutilização de token corretamente, ou sobrescreve o refresh token armazenado numa corrida entre duas requisições.
Caminho prático de correção
Correções normalmente são diretas quando tratadas em ordem.
Primeiro, adicione uma pequena tolerância ao validar claims de tempo (normalmente 30–120 segundos). Isso pode evitar expirações falsas causadas por desvio de relógio.
Em seguida, torne o refresh confiável. Quando um access token expirar, faça um único refresh e tente novamente a requisição original. Se múltiplas requisições encontrarem expiração ao mesmo tempo, garanta que apenas uma faça o refresh e as outras esperem por ela.
Finalmente, aperte o armazenamento. Use cookies HTTP-only e seguros para web quando possível, e evite colocar refresh tokens em localStorage. No mobile, use o armazenamento seguro da plataforma.
Se seu protótipo foi gerado rápido e a autenticação está instável, a FixMyMess pode rodar uma auditoria de código gratuita para apontar onde lógica de refresh, rotação ou padrões de armazenamento estão falhando no uso real e então consertar para produção.
Checklist rápido e próximos passos
Quando problemas de JWT parecem “aleatórios”, eles geralmente não são. Um token falha por um conjunto pequeno de motivos: os números nele estão errados, o relógio do servidor está fora, o fluxo de refresh está incompleto ou seu app armazena tokens de forma frágil.
Comece com provas rápidas, não suposições. Copie um token que falhou, decodifique-o e anote exp, iat, iss, aud e o id/subject. Compare isso com o que sua API está realmente validando neste ambiente.
Aqui está um checklist curto que pega a maioria dos problemas de protótipo:
- Decodifique um token que falhou e confirme que
expestá no futuro eiatnão está “no futuro” comparado ao tempo do servidor API. - Verifique o tempo da API: cheque o relógio do servidor, tempo do container e sincronização do host. Mesmo alguns minutos de drift podem causar falhas repentinas.
- Confirme configuração de assinatura: assegure que o segredo/chave privada está correta para este ambiente (dev vs preview vs prod) e que você não está misturando chaves entre serviços.
- Valide audience/issuer:
audeissdevem bater com o que a API espera em todos os ambientes (especialmente deployments preview). - Teste comportamento de refresh: o endpoint de refresh retorna um novo access token de forma confiável, e a rotação do refresh token não invalida usuários que ainda têm sessão ativa.
Depois disso, faça um teste end-to-end como um usuário real: faça login, espere o access token expirar e então faça uma chamada API e observe o app renovar e tentar novamente. Se falhar, procure estes padrões: refresh token não enviado (flags do cookie erradas), refresh token sobrescrito durante rotação ou o cliente nunca tentar novamente a requisição original.
Armazenamento é sua última verificação rápida: evite colocar refresh tokens em localStorage. Para apps web, um cookie HTTP-only costuma ser o padrão mais seguro. Para mobile, use armazenamento seguro da plataforma.
Se seu protótipo gerado por IA (Lovable, Bolt, v0, Cursor, Replit) tem auth instável e você precisa deixá-lo pronto para produção rápido, a FixMyMess pode rodar uma auditoria de código gratuita para localizar o que está quebrando e então reparar ou reconstruir o fluxo de forma segura.