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ítica | Quando expira | Uso típico |
|---|---|---|
| Idle timeout | Sem atividade por X | Apps com risco de abandono (PC compartilhado) |
| Absolute timeout | Após Y desde login | Ambientes regulados, alto risco |
| Combinada | O que ocorrer primeiro | Maioria dos sistemas |
Modelo de dados sugerido para expiração
Armazene na sessão campos que permitam validar ambos os timeouts:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
created_at: quando a sessão foi criadalast_seen_at: último uso observadoidle_expires_at: opcional (pode ser derivado delast_seen_at)absolute_expires_at: opcional (pode ser derivado decreated_at)revoked_at: se revogada
Passo a passo: validação de expiração em cada requisição
- Carregar sessão pelo identificador (ID) recebido.
- Checar revogação: se
revoked_atnão for nulo, negar. - Checar absolute timeout: se
now > created_at + ABS_TTL, negar. - Checar idle timeout: se
now > last_seen_at + IDLE_TTL, negar. - 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 VALIDRotaçã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
- Autenticar o usuário (credenciais válidas).
- Criar nova sessão com novo ID (criptograficamente aleatório) e copiar apenas o necessário (ex.:
user_id,created_atnovo,last_seen_at). - Revogar a sessão antiga (se existir) ou marcá-la como substituída.
- Enviar o novo ID ao cliente (cookie/headers conforme seu padrão).
- 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_at2) 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: INVALID3) 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_iddevice_idcreated_at,last_seen_atip_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:
- Usuário escolhe uma sessão/dispositivo na lista.
- Servidor verifica que a sessão pertence ao usuário.
- Servidor marca
revoked_aterevoke_reason = "user_revoked". - 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_atPasso a passo: registrar login/logout
- No login: após criar/rotacionar sessão, inserir evento
logincomsession_id_hash. - No logout: marcar sessão como revogada e inserir evento
logout(ousession_revokedcom reasonlogout). - 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: retryTé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_afterno usuário, oudevice_revoked_afterpor 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: INVALIDTé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
- Validar credenciais.
- Carregar
user.session_version. - Criar nova sessão com
user_session_versionatual. - Se existir sessão prévia (anônima ou antiga), revogar (rotação).
- Aplicar política de máximo de sessões: listar sessões ativas do usuário e revogar excedentes.
- Registrar evento
logine, se aplicável,session_rotated.
Fluxo: logout (revogação idempotente)
- Identificar sessão atual.
- Executar update idempotente para setar
revoked_atse ainda não estiver setado. - Registrar evento
logout. - Responder sucesso mesmo se já estava revogada (evita vazamento de informação e simplifica concorrência).
Fluxo: “sair de todos os dispositivos”
- Incrementar
user.session_version(ou setarsessions_revoked_after = now). - Opcional: marcar como revogadas as sessões existentes para refletir na lista de “sessões ativas” imediatamente.
- Registrar evento
session_revokedcom reasonglobal_logout.
Fluxo: revogar por dispositivo
- Receber
device_ida revogar. - Revogar todas as sessões com aquele
device_iddo usuário (update em lote comrevoked_atidempotente) ou usar cutoff por dispositivo. - Registrar evento com reason
device_removed.
Cuidados adicionais que evitam problemas comuns
- Throttling de last_seen_at: para reduzir escrita, atualize
last_seen_atapenas 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.