Refresh tokens e expiração segura em autenticação por tokens

Capítulo 7

Tempo estimado de leitura: 10 minutos

+ Exercício

O que são refresh tokens e por que eles existem

Em autenticação por tokens, é comum separar credenciais em dois artefatos com objetivos diferentes:

  • Access token: curto, enviado em chamadas à API para autorizar requisições. Deve expirar rapidamente para reduzir impacto em caso de vazamento.
  • Refresh token: longo, usado apenas para obter novos access tokens sem exigir login novamente. Deve ter uso restrito (somente no endpoint de renovação) e controles fortes de revogação e detecção de abuso.

A ideia central é limitar a janela de ataque do access token e, ao mesmo tempo, manter boa experiência do usuário com renovação silenciosa via refresh token.

Políticas recomendadas de tempo de vida (TTL)

ArtefatoTTL típicoObservações
Access token5 a 15 minutosCurto para reduzir impacto de roubo; não deve ser “revogável” por padrão (revogação em massa é cara), então confie na expiração curta.
Refresh token7 a 30 diasMais longo, mas com revogação/rotação; pode ter “idle timeout” (expira se não for usado por X dias).
Refresh token (família/chain)Até 30 a 90 diasLimite máximo da sessão contínua (mesmo com renovações), exigindo reautenticação após esse período.

Além do TTL, defina limites por dispositivo e por usuário (por exemplo, no máximo 5 dispositivos ativos). Isso reduz superfície de ataque e facilita “logout global”.

Onde armazenar o refresh token

Opção preferencial em aplicações web: cookie httpOnly

Para aplicações web (browser), o refresh token deve preferencialmente ficar em cookie httpOnly para reduzir risco de exfiltração via XSS. Recomendações:

  • HttpOnly: impede leitura via JavaScript.
  • Secure: somente via HTTPS.
  • SameSite: Lax em muitos casos; None se houver cross-site (ex.: front e API em domínios diferentes), sempre com Secure.
  • Path restrito: por exemplo, /auth/refresh para enviar o cookie apenas ao endpoint de renovação.
Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Lax; Path=/auth/refresh; Max-Age=2592000

Access token pode ser retornado no corpo da resposta e mantido em memória (state) no front-end, reduzindo persistência. Evite armazenar access token em localStorage quando possível.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

Apps mobile/desktop

Em mobile/desktop, use armazenamento seguro do sistema (Keychain/Keystore/DPAPI). O refresh token não deve ficar em armazenamento “plano”.

Modelo de dados para refresh token (persistência e revogação)

Para permitir revogação, rotação e detecção de reutilização, trate refresh tokens como registros persistidos. Um modelo comum:

  • token_id (jti): identificador único do refresh token.
  • user_id
  • device_id (ou session_id): identifica o dispositivo/sessão.
  • expires_at
  • revoked_at (nullable)
  • replaced_by_token_id (nullable): aponta para o próximo token após rotação.
  • family_id: identifica a “cadeia” de rotação (útil para logout global e reuse detection).
  • last_used_at (opcional): para políticas de idle timeout.
  • metadata (opcional): IP, user-agent, geolocalização aproximada, etc. (use com cuidado e privacidade).

Armazenamento: nunca persista o refresh token em texto puro. Armazene um hash (ex.: SHA-256) do token e compare por hash.

// Exemplo conceitual: armazenar somente hash do refresh token (não o token em si)

Fluxo completo: emissão, renovação e revogação

1) Emissão (login)

No login bem-sucedido:

  • Emita um access token com TTL curto.
  • Emita um refresh token com TTL longo, associado a user_id e device_id.
  • Persista o refresh token (hash) com status ativo.
  • Envie refresh token em cookie httpOnly (web) e access token no corpo.
POST /auth/login
200 OK (body JSON) { accessToken: "...", expiresIn: 900 }
Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Lax; Path=/auth/refresh; Max-Age=2592000

2) Renovação (refresh)

Quando o access token expira (ou está prestes a expirar), o cliente chama o endpoint de refresh. O refresh token é enviado automaticamente via cookie (web) ou via header/body (mobile/desktop, conforme seu padrão).

POST /auth/refresh

O servidor deve:

  • Extrair o refresh token e calcular o hash.
  • Encontrar o registro correspondente (por token_hash ou por jti embutido).
  • Validar: não expirou, não foi revogado, pertence ao usuário/dispositivo esperado, e está “atual” na cadeia (ver rotação abaixo).
  • Emitir novo access token.
  • Rotacionar o refresh token (one-time use): emitir um novo refresh token e invalidar o anterior, marcando replaced_by_token_id.
  • Atualizar cookie com o novo refresh token.

3) Revogação (logout)

No logout do dispositivo atual:

  • Revogue o refresh token atual (registro) e, opcionalmente, toda a família associada ao dispositivo.
  • Limpe o cookie do refresh token.
POST /auth/logout
Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Lax; Path=/auth/refresh; Max-Age=0

Rotação de refresh token (one-time use) e reuse detection

Por que rotacionar

Se um refresh token for roubado, um atacante poderia renovar access tokens por muito tempo. Com rotação “one-time use”, cada refresh token só pode ser usado uma vez. Isso cria um sinal forte de comprometimento quando um token antigo reaparece.

Como implementar rotação

Ao processar /auth/refresh com um refresh token válido:

  • Marque o token atual como “consumido” e registre replaced_by_token_id apontando para o novo token.
  • Crie um novo refresh token (novo jti) na mesma family_id.
  • Retorne novo access token e novo refresh token (cookie).

Detecção de reutilização (reuse detection)

Reuse detection ocorre quando um refresh token que já foi rotacionado (ou revogado) é apresentado novamente. Isso geralmente indica:

  • Roubo do refresh token antigo (ex.: malware, interceptação, vazamento).
  • Condição de corrida (duas abas tentando refresh ao mesmo tempo) — deve ser tratada com cuidado para não gerar falsos positivos.

Regra prática: se um refresh token já possui replaced_by_token_id (ou está marcado como revogado/consumido) e aparece novamente, trate como evento suspeito.

Ação recomendada ao detectar reuse:

  • Revogar toda a família (family_id) para cortar o atacante.
  • Exigir reautenticação.
  • Registrar evento de segurança (audit log) com IP/user-agent.

Tratando condição de corrida (abas múltiplas)

Dois refresh simultâneos podem acontecer. Estratégias:

  • Lock por token_id: ao iniciar refresh, faça lock transacional do registro do refresh token.
  • Janela de tolerância curta: permitir que o token “anterior” seja aceito por poucos segundos apenas se ainda não houver evidência de uso malicioso (mais complexo e pode enfraquecer reuse detection).
  • Cliente com fila: no front-end, centralize o refresh para que apenas uma requisição de refresh ocorra por vez.

Limites por dispositivo e “logout global”

Identificando dispositivo/sessão

Associe refresh tokens a um device_id (gerado no cliente e enviado no login) ou a um session_id criado no servidor. Isso permite:

  • Listar sessões ativas do usuário.
  • Revogar apenas um dispositivo.
  • Aplicar limite de dispositivos (ex.: máximo 5).

Política de limite de dispositivos

Ao exceder o limite:

  • Revogue as sessões mais antigas (por last_used_at ou created_at).
  • Ou bloqueie novo login até o usuário encerrar uma sessão existente.

Logout global

Para logout global (encerrar todas as sessões):

  • Revogue todos os refresh tokens do usuário (ou todas as famílias).
  • Opcional: incremente um user_token_version (ou session_epoch) no banco e inclua esse valor no access token; a API rejeita access tokens com versão antiga. Isso ajuda a reduzir a janela até o access token expirar, mas adiciona verificação extra.

Diagrama textual do fluxo (emissão, refresh, rotação e revogação)

[Login] Client --(credenciais)--> POST /auth/login --> Server
  Server: valida usuário
  Server: emite AccessToken (curto)
  Server: cria RefreshToken R1 (longo), family=F, device=D, status=ativo
  Server: Set-Cookie(refresh_token=R1, httpOnly)
  Client: usa AccessToken nas APIs

[Uso normal] Client --(Authorization: Bearer AT1)--> API
  API: valida AT1 (expira rápido)

[Refresh] Client --(cookie refresh_token=R1)--> POST /auth/refresh
  Server: valida R1 (ativo, não expirado, não revogado)
  Server: ROTACIONA: marca R1 como substituído por R2
  Server: cria R2 (ativo) na mesma family F
  Server: emite novo AccessToken AT2
  Server: Set-Cookie(refresh_token=R2, httpOnly)

[Reuse detection] Attacker/Client --(cookie refresh_token=R1 antigo)--> POST /auth/refresh
  Server: detecta R1 já substituído/revogado
  Server: revoga family F inteira (R2 e futuros)
  Server: responde erro e exige reauth

[Logout dispositivo] Client --> POST /auth/logout
  Server: revoga refresh token atual (e opcionalmente family do device)
  Server: limpa cookie

[Logout global] Client --> POST /auth/logout-all
  Server: revoga todos refresh tokens do user (todas families)
  Server: limpa cookie do dispositivo atual

Casos de erro e respostas recomendadas

Refresh token ausente

Cenário: cookie não enviado (expirou, foi limpo, domínio/path incorreto).

  • Resposta: 401 Unauthorized
  • Ação do cliente: redirecionar para login.
{ "error": "refresh_token_missing" }

Refresh token expirado

Cenário: expires_at passou.

  • Resposta: 401 Unauthorized
  • Ação do servidor: revogar/limpar registro expirado (higienização) e limpar cookie.
  • Ação do cliente: login novamente.
{ "error": "refresh_token_expired" }

Refresh token revogado

Cenário: logout, limite de dispositivos, logout global, ação administrativa.

  • Resposta: 401 Unauthorized
  • Ação do cliente: login novamente.
{ "error": "refresh_token_revoked" }

Refresh token suspeito (reuse detection)

Cenário: token já foi rotacionado e reaparece; ou inconsistência de cadeia.

  • Resposta: 401 Unauthorized (ou 403 Forbidden, conforme padrão do seu sistema)
  • Ação do servidor: revogar família inteira, registrar evento, opcionalmente notificar usuário.
  • Ação do cliente: forçar reautenticação e invalidar estado local.
{ "error": "refresh_token_reuse_detected" }

Refresh token válido, mas dispositivo excedeu limite

Cenário: política de máximo de sessões/dispositivos.

  • Resposta: 401 Unauthorized ou 409 Conflict (se quiser indicar conflito de política)
  • Ação do servidor: revogar sessões antigas automaticamente ou exigir que o usuário gerencie dispositivos.
{ "error": "device_limit_exceeded" }

Passo a passo prático: endpoints e lógica essencial

Endpoint: POST /auth/login

  • Validar credenciais.
  • Criar device_id (se não vier) e family_id.
  • Gerar refresh token aleatório forte (ex.: 256 bits) e salvar hash.
  • Emitir access token curto.
  • Setar cookie httpOnly com refresh token.

Endpoint: POST /auth/refresh

  • Ler refresh token do cookie.
  • Buscar registro pelo hash.
  • Validar: ativo, não expirado, não revogado.
  • Checar reuse: se já foi substituído/revogado, revogar família e negar.
  • Rotacionar: criar novo refresh token e marcar o anterior como substituído.
  • Emitir novo access token e setar novo cookie.

Endpoint: POST /auth/logout

  • Identificar refresh token atual (cookie).
  • Revogar o registro correspondente.
  • Limpar cookie.

Endpoint: POST /auth/logout-all

  • Revogar todos os refresh tokens do usuário (todas as famílias).
  • Limpar cookie do dispositivo atual.

Checklist de segurança específico para refresh tokens

  • Refresh token só deve ser aceito em /auth/refresh (e talvez /auth/logout), nunca em endpoints de API comuns.
  • Use cookie HttpOnly + Secure + SameSite adequado e Path restrito.
  • Persistir apenas hash do refresh token.
  • Rotação one-time use com replaced_by_token_id e family_id.
  • Reuse detection com revogação da família inteira.
  • Limite de dispositivos/sessões por usuário.
  • Logs de auditoria para eventos: refresh, revogação, reuse detection.
  • Proteção contra CSRF no endpoint de refresh quando usar cookies (ex.: double submit cookie ou header anti-CSRF), especialmente se SameSite=None.

Agora responda o exercício sobre o conteúdo:

Em um fluxo com access token de curta duração e refresh token com rotação (one-time use), qual ação é mais adequada quando o servidor detecta que um refresh token antigo, já substituído, foi apresentado novamente?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Se um refresh token já rotacionado reaparece, isso indica possível comprometimento (reuse detection). A prática recomendada é revogar toda a family_id para cortar o atacante e exigir novo login, registrando o evento.

Próximo capitúlo

Armazenamento seguro de credenciais e tokens no cliente e no servidor

Arrow Right Icon
Capa do Ebook gratuito Autenticação e Autorização no Back-end: Sessões, JWT e Boas Práticas
39%

Autenticação e Autorização no Back-end: Sessões, JWT e Boas Práticas

Novo curso

18 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.