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)
| Artefato | TTL típico | Observações |
|---|---|---|
| Access token | 5 a 15 minutos | Curto 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 token | 7 a 30 dias | Mais 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 dias | Limite 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:Laxem muitos casos;Nonese houver cross-site (ex.: front e API em domínios diferentes), sempre comSecure.Pathrestrito: por exemplo,/auth/refreshpara enviar o cookie apenas ao endpoint de renovação.
Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Lax; Path=/auth/refresh; Max-Age=2592000Access 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.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
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_idedevice_id. - Persista o refresh token (hash) com status ativo.
- Envie refresh token em cookie httpOnly (web) e access token no corpo.
POST /auth/login200 OK (body JSON) { accessToken: "...", expiresIn: 900 }Set-Cookie: refresh_token=...; HttpOnly; Secure; SameSite=Lax; Path=/auth/refresh; Max-Age=25920002) 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/refreshO servidor deve:
- Extrair o refresh token e calcular o hash.
- Encontrar o registro correspondente (por
token_hashou porjtiembutido). - 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/logoutSet-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Lax; Path=/auth/refresh; Max-Age=0Rotaçã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_idapontando para o novo token. - Crie um novo refresh token (novo
jti) na mesmafamily_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_atoucreated_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(ousession_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 atualCasos 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(ou403 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 Unauthorizedou409 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) efamily_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+SameSiteadequado ePathrestrito. - Persistir apenas hash do refresh token.
- Rotação one-time use com
replaced_by_token_idefamily_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.