Gerenciamento de sessão: expiração, rotação e revogação

Capítulo 4

Tempo estimado de leitura: 12 minutos

+ Exercício

O que significa “gerenciar sessão” no back-end

Depois que uma sessão é criada (por exemplo, após um login), o back-end precisa controlar o ciclo de vida dela: quando expira, quando deve trocar seu identificador para reduzir riscos, e como revogar (invalidar) sessões de forma confiável. Um bom gerenciamento também considera múltiplos dispositivos, lista de sessões ativas e auditoria mínima (eventos de login/logout), além de lidar com concorrência para evitar que uma sessão revogada continue válida por causa de condições de corrida.

Expiração de sessão: idle timeout vs absolute timeout

Idle timeout (expiração por inatividade)

No idle timeout, a sessão expira se não houver atividade por um período. A cada requisição válida, o servidor atualiza o “último acesso” e empurra a expiração para frente. É útil para reduzir risco quando o usuário abandona um dispositivo logado.

  • Prós: melhora a segurança em dispositivos compartilhados; reduz janela de exposição.
  • Contras: pode incomodar usuários ativos em longas jornadas se o sistema não renova corretamente; exige atualização frequente do estado da sessão.

Absolute timeout (expiração absoluta)

No absolute timeout, a sessão expira após um tempo máximo contado a partir do login (ou criação da sessão), independentemente de atividade. É uma barreira contra sessões “eternas” em caso de comprometimento.

  • Prós: limita o tempo total de validade; simples de raciocinar.
  • Contras: pode forçar reautenticação durante uso contínuo.

Estratégia combinada (recomendável)

Na prática, combina-se os dois: a sessão expira se ficar inativa por X minutos ou se ultrapassar Y horas desde o login. Isso equilibra usabilidade e segurança.

PolíticaQuando expiraUso típico
Idle timeoutSem atividade por XApps com risco de abandono (PC compartilhado)
Absolute timeoutApós Y desde loginAmbientes regulados, alto risco
CombinadaO que ocorrer primeiroMaioria dos sistemas

Modelo de dados sugerido para expiração

Armazene na sessão campos que permitam validar ambos os timeouts:

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

  • created_at: quando a sessão foi criada
  • last_seen_at: último uso observado
  • idle_expires_at: opcional (pode ser derivado de last_seen_at)
  • absolute_expires_at: opcional (pode ser derivado de created_at)
  • revoked_at: se revogada

Passo a passo: validação de expiração em cada requisição

  1. Carregar sessão pelo identificador (ID) recebido.
  2. Checar revogação: se revoked_at não for nulo, negar.
  3. Checar absolute timeout: se now > created_at + ABS_TTL, negar.
  4. Checar idle timeout: se now > last_seen_at + IDLE_TTL, negar.
  5. Atualizar last_seen_at (com cuidado para não gerar escrita excessiva; ver seção de concorrência).
// Pseudocódigo (independente de linguagem) para validação de sessão
function validateSession(sessionId, now):
  s = db.sessions.findById(sessionId)
  if s == null: return INVALID
  if s.revoked_at != null: return INVALID
  if now > s.created_at + ABS_TTL: return INVALID
  if now > s.last_seen_at + IDLE_TTL: return INVALID
  // opcional: atualizar last_seen_at com throttling
  return VALID

Rotação de identificadores: mitigando session fixation

Rotação de identificador significa emitir um novo ID de sessão e invalidar (ou tornar obsoleto) o anterior em momentos sensíveis. O objetivo é reduzir riscos como session fixation, onde um atacante tenta “fixar” um ID conhecido e induzir a vítima a autenticar com ele.

Quando rotacionar

  • Após login (obrigatório em muitos cenários): se existia uma sessão anônima antes, não reaproveite o mesmo ID.
  • Após elevação de privilégio: por exemplo, quando o usuário passa por 2FA, ou muda de papel/permissão.
  • Após mudança de credenciais: troca de senha, reset de senha.
  • Periodicamente: em sessões longas, pode ser útil rotacionar a cada N horas.

Passo a passo: rotação segura do ID

  1. Autenticar o usuário (credenciais válidas).
  2. Criar nova sessão com novo ID (criptograficamente aleatório) e copiar apenas o necessário (ex.: user_id, created_at novo, last_seen_at).
  3. Revogar a sessão antiga (se existir) ou marcá-la como substituída.
  4. Enviar o novo ID ao cliente (cookie/headers conforme seu padrão).
  5. Garantir atomicidade: evitar janela onde ambas ficam válidas por muito tempo (ver concorrência).
// Exemplo de fluxo transacional
transaction:
  old = db.sessions.findById(oldSessionId)
  new = db.sessions.insert({
    id: randomId(),
    user_id: userId,
    created_at: now,
    last_seen_at: now,
    parent_session_id: old?.id
  })
  if old != null:
    db.sessions.update(old.id, { revoked_at: now, revoke_reason: "rotated" })
commit
setCookie("sid", new.id)

Observação importante: não “recicle” ID

Evite manter o mesmo identificador e apenas atualizar atributos. A rotação deve gerar um novo ID imprevisível e invalidar o anterior.

Revogação de sessão: estratégias e trade-offs

Revogar é tornar uma sessão inválida antes de expirar naturalmente. Isso é necessário para logout, suspeita de comprometimento, remoção de dispositivo, mudança de senha, ou políticas administrativas.

1) Lista de bloqueio (blocklist/denylist)

Você mantém uma lista de IDs revogados (ou hashes desses IDs) e consulta essa lista em cada requisição.

  • Prós: simples de entender; revogação imediata.
  • Contras: custo de consulta extra; precisa de expiração/limpeza da lista; cuidado com volume.

Boas práticas:

  • Armazene hash do ID (ex.: SHA-256) para reduzir impacto se a base vazar.
  • Defina TTL na entrada da blocklist até o absolute timeout da sessão.
// Estrutura simples de blocklist
revoked_sessions:
  - sid_hash
  - revoked_at
  - expires_at

2) Versão de sessão (session version) por usuário

Você mantém um contador/versão no registro do usuário (ex.: session_version). Cada sessão armazena a versão vigente no momento da criação. Ao revogar “todas as sessões”, você incrementa a versão no usuário. Sessões com versão antiga passam a ser inválidas.

  • Prós: excelente para “logout global” e troca de senha; custo baixo.
  • Contras: não revoga seletivamente uma única sessão/dispositivo sem mecanismo adicional.
// Validação
if session.user_session_version != user.session_version: INVALID

3) Invalidação por usuário/dispositivo (sessões persistidas)

Você persiste cada sessão como um registro e consegue revogar por session_id, por user_id, por device_id ou por critérios (ex.: “todas exceto a atual”). É a base para “sessões ativas”.

  • Prós: revogação granular; suporta múltiplos dispositivos; auditoria natural.
  • Contras: exige armazenamento e consultas; precisa lidar com concorrência.

Escolhendo uma abordagem

Um desenho comum e robusto combina:

  • Tabela de sessões persistidas (para granularidade e lista de sessões ativas)
  • session_version por usuário (para revogação global rápida)
  • Revogação por registro (revoked_at) em vez de blocklist separada, quando você já consulta a sessão no banco/cache

Múltiplos dispositivos e “sessões ativas”

Identificando dispositivo

Para gerenciar múltiplos dispositivos, associe a sessão a um identificador de dispositivo. Evite depender apenas de User-Agent (instável). Opções:

  • device_id gerado no cliente e armazenado de forma persistente (ex.: storage seguro) e enviado ao servidor.
  • device_id emitido pelo servidor na primeira autenticação e armazenado pelo cliente.

Campos úteis por sessão:

  • id (session_id)
  • user_id
  • device_id
  • created_at, last_seen_at
  • ip_first, ip_last (opcional)
  • ua_hash (hash do user-agent, opcional)
  • revoked_at, revoke_reason

Listando sessões ativas

Para exibir “onde você está logado”, filtre sessões não revogadas e não expiradas. Exemplo de consulta lógica:

SELECT id, device_id, created_at, last_seen_at, ip_last
FROM sessions
WHERE user_id = :userId
  AND revoked_at IS NULL
  AND created_at >= NOW() - INTERVAL 'ABS_TTL'
  AND last_seen_at >= NOW() - INTERVAL 'IDLE_TTL'
ORDER BY last_seen_at DESC;

Revogar uma sessão específica (remover dispositivo)

Fluxo típico:

  1. Usuário escolhe uma sessão/dispositivo na lista.
  2. Servidor verifica que a sessão pertence ao usuário.
  3. Servidor marca revoked_at e revoke_reason = "user_revoked".
  4. Requisições futuras com aquele ID falham na validação.

Política de limite de sessões

Você pode impor um máximo de sessões por usuário (ex.: 5). Ao exceder, revogue as mais antigas ou as menos recentes:

// Pseudocódigo
active = listActiveSessions(userId)
if active.count >= MAX:
  revoke(oldestOrLeastRecentlyUsed(active))

Auditoria mínima: registrando eventos de sessão (login/logout)

Auditoria mínima significa registrar o suficiente para investigar incidentes e suportar suporte ao usuário, sem coletar dados excessivos. Registre eventos como login bem-sucedido, logout, revogação e rotação.

O que registrar

  • event_type: login, logout, session_revoked, session_rotated, login_failed (opcional)
  • user_id (quando aplicável)
  • session_id (ou hash)
  • device_id (se houver)
  • timestamp
  • ip (preferencialmente o IP observado)
  • ua_hash (hash do user-agent, opcional)
  • reason: ex.: user_action, password_change, admin_action, rotation

Exemplo de tabela de auditoria

session_events:
  - id
  - event_type
  - user_id
  - session_id_hash
  - device_id
  - ip
  - ua_hash
  - reason
  - created_at

Passo a passo: registrar login/logout

  1. No login: após criar/rotacionar sessão, inserir evento login com session_id_hash.
  2. No logout: marcar sessão como revogada e inserir evento logout (ou session_revoked com reason logout).
  3. Em revogação administrativa: inserir evento com reason admin_action.

Concorrência e race conditions ao revogar

Condições de corrida aparecem quando duas ou mais requisições tentam validar/atualizar/revogar a mesma sessão ao mesmo tempo. Exemplos comuns:

  • Uma requisição A valida a sessão como ativa; antes de atualizar last_seen_at, outra requisição B revoga; A continua e “ressuscita” a sessão se a atualização não for cuidadosa.
  • Logout em uma aba enquanto outra aba faz requisições em paralelo.
  • Rotação de ID enquanto requisições antigas ainda estão em trânsito.

Regra de ouro: revogação deve ser monotônica

Uma vez revogada, a sessão não pode voltar a ser ativa. Isso implica que atualizações de “atividade” não devem limpar revoked_at e devem falhar se a sessão já estiver revogada.

Técnica 1: atualização condicional (compare-and-set)

Ao atualizar last_seen_at, faça update apenas se revoked_at IS NULL e se o novo timestamp for maior. Assim, se a sessão foi revogada entre a leitura e a escrita, a atualização não acontece.

// Exemplo SQL: atualizar last_seen_at sem ressuscitar sessão
UPDATE sessions
SET last_seen_at = :now
WHERE id = :sid
  AND revoked_at IS NULL
  AND last_seen_at < :now;

Para revogar:

// Revogação idempotente
UPDATE sessions
SET revoked_at = COALESCE(revoked_at, :now), revoke_reason = :reason
WHERE id = :sid;

Técnica 2: controle de versão (optimistic locking)

Adicione um campo row_version (inteiro). Cada update incrementa. Ao atualizar, exija que a versão não tenha mudado desde a leitura. Se mudou, refaça o fluxo.

// Pseudocódigo
s = loadSession(sid)
if s.revoked_at != null: INVALID
ok = update sessions set last_seen_at=now, row_version=row_version+1
     where id=sid and row_version=s.row_version and revoked_at is null
if !ok: retry

Técnica 3: revogação por “cutoff” (timestamp de invalidação)

Para revogar em massa por usuário/dispositivo sem tocar em todas as linhas, mantenha um “cutoff”:

  • user_sessions_revoked_after no usuário, ou
  • device_revoked_after por dispositivo.

Na validação, se session.created_at <= cutoff, a sessão é inválida. Isso reduz escrita em lote e evita janelas inconsistentes.

// Validação com cutoff
if session.created_at <= user.sessions_revoked_after: INVALID

Técnica 4: rotação com janela controlada

Durante rotação, pode haver requisições em trânsito com o ID antigo. Duas abordagens:

  • Revogar imediatamente o ID antigo: mais seguro, pode causar falhas em requisições concorrentes.
  • Grace period curto: marcar sessão antiga como “rotated” e aceitar por poucos segundos apenas para finalizar requisições idempotentes. Exige cuidado para não abrir brecha; se usar, limite por tempo e por tipo de endpoint.

Implementação prática: desenho de tabela e fluxos principais

Esquema de tabela de sessões (exemplo)

sessions:
  - id (PK)
  - user_id (index)
  - device_id (index, nullable)
  - created_at
  - last_seen_at
  - revoked_at (nullable, index)
  - revoke_reason (nullable)
  - parent_session_id (nullable)
  - user_session_version (int)
  - row_version (int)
  - ip_first (nullable)
  - ip_last (nullable)
  - ua_hash (nullable)

Fluxo: login com rotação e limites de sessões

  1. Validar credenciais.
  2. Carregar user.session_version.
  3. Criar nova sessão com user_session_version atual.
  4. Se existir sessão prévia (anônima ou antiga), revogar (rotação).
  5. Aplicar política de máximo de sessões: listar sessões ativas do usuário e revogar excedentes.
  6. Registrar evento login e, se aplicável, session_rotated.

Fluxo: logout (revogação idempotente)

  1. Identificar sessão atual.
  2. Executar update idempotente para setar revoked_at se ainda não estiver setado.
  3. Registrar evento logout.
  4. Responder sucesso mesmo se já estava revogada (evita vazamento de informação e simplifica concorrência).

Fluxo: “sair de todos os dispositivos”

  1. Incrementar user.session_version (ou setar sessions_revoked_after = now).
  2. Opcional: marcar como revogadas as sessões existentes para refletir na lista de “sessões ativas” imediatamente.
  3. Registrar evento session_revoked com reason global_logout.

Fluxo: revogar por dispositivo

  1. Receber device_id a revogar.
  2. Revogar todas as sessões com aquele device_id do usuário (update em lote com revoked_at idempotente) ou usar cutoff por dispositivo.
  3. Registrar evento com reason device_removed.

Cuidados adicionais que evitam problemas comuns

  • Throttling de last_seen_at: para reduzir escrita, atualize last_seen_at apenas se passou um intervalo mínimo (ex.: 1–5 minutos). Ainda assim, mantenha a atualização condicional para não reativar sessão revogada.
  • Limpeza (garbage collection): remova sessões expiradas/revogadas após um período, mantendo eventos de auditoria pelo tempo necessário.
  • Hash do session_id em logs: nunca logue o ID puro; use hash para correlacionar eventos sem expor segredo.
  • Idempotência: endpoints de logout/revogação devem ser idempotentes para suportar repetição e concorrência.

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

Ao combinar idle timeout e absolute timeout no gerenciamento de sessões, qual é o comportamento esperado de expiração e por que essa estratégia é recomendável?

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

Você errou! Tente novamente.

Na estratégia combinada, a sessão é invalidada pelo primeiro critério atingido: inatividade (idle) ou tempo máximo desde a criação (absolute). Isso reduz o risco de abandono e também impede sessões “eternas”, equilibrando segurança e experiência.

Próximo capitúlo

Autenticação com tokens no back-end: conceitos e fluxo com JWT

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

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.