Tokens opacos (opaque bearer tokens)
Um crachá te dá acesso a um prédio comercial. Ele não tem nome, não tem foto, e não tem nada legível por dentro. Você aproxima o crachá, um leitor confere numa lista, e a porta abre ou não abre.
Um opaque bearer token (token opaco de portador) é esse crachá. Uma string aleatória e impossível de adivinhar, sem nada dentro dela para ser lido.
Sem estado não significa que o servidor esquece tudo
Essa expressão confunde bastante gente, então vale esclarecer isso logo de cara.
Sem estado (stateless) significa que o servidor não mantém sessão: nenhum objeto rastreando um usuário entre requisições. Ele ainda tem um banco de dados, ainda tem usuários, ainda armazena coisas. O que ele deixa de manter é um registro de que essa pessoa está logada no momento.
A requisição precisa trazer tudo que o servidor precisa para descobrir quem está perguntando.
O servidor para de lembrar de você; o cliente passa a se provar a cada requisição.
Duas palavras explicam o nome. Opaco significa que o servidor não consegue ler nada dentro do token, porque não há nada lá dentro. Não é codificado nem estruturado, é uma string aleatória. Bearer (portador) significa que quem o detém, o usa.
A única coisa que ele deixa de guardar é uma anotação dizendo que você está logado agora. Essa anotação é o que o token substitui.
O fluxo
Seis passos, e o formato vai parecer familiar:
- O usuário envia um nome de usuário e uma senha.
- O servidor os verifica.
- Em caso de sucesso, o servidor gera um token aleatório.
- O servidor armazena um pequeno mapeamento de token para usuário e envia o token ao cliente.
- O cliente o guarda e o anexa em toda requisição posterior no cabeçalho
Authorization. - O servidor consulta o token, encontra o usuário, e a requisição é autenticada.
GET /api/orders
Authorization: Bearer 7f3a9c1e5b28d4a06e91f7c3Comparado com sessões e cookies, as diferenças estão em três pontos:
| Sessão com estado | Token opaco de portador | |
|---|---|---|
| O servidor armazena | Um objeto de sessão completo | Uma linha de token para usuário |
| O cliente armazena | Um cookie contendo um id de sessão | O próprio token |
| Ele viaja | Automaticamente, pelo navegador | Explicitamente, no código que você escreve |
| Serve para | Aplicações web tradicionais | APIs e aplicativos mobile |
Essa terceira linha importa mais do que parece. Um cookie viaja junto sem que ninguém peça. Um bearer token é anexado pelo seu próprio código de cliente, em toda requisição, de forma deliberada.
A diferença está em quanto o servidor guarda e quem faz o envio. Mesmo esqueleto, distribuição de peso diferente.
Cinco formas como isso dá errado
Quase nenhum dos riscos está em como o modelo funciona. Está em como o token é armazenado, quanto tempo ele dura, e o quão bem cuidada é a tabela de consulta.
| O que dá errado | Por que isso importa | A correção | |
|---|---|---|---|
| 1 | Armazenamento inseguro | Um token no localStorage, numa variável simples ou num cookie desprotegido é legível por qualquer script na página. Um bug de XSS e o atacante fica indistinguível do usuário | Guarde-o em algum lugar mais difícil de alcançar, e limite a frequência com que ele fica exposto |
| 2 | Tokens de vida longa | Um token válido por semanas significa que um token vazado é válido por semanas | Janelas de expiração curtas |
| 3 | Sem revogação | Tokens não expiram sozinhos nem param de funcionar no logout. Uma expiração que nada verifica é só decoração | Verifique a expiração em toda requisição, delete no logout |
| 4 | Acúmulo na tabela de consulta | Todo login grava uma linha, então as linhas se acumulam e as consultas ficam lentas. Um atacante pode forçar isso martelando o endpoint de login, o que é uma negação de serviço | Um job agendado removendo tokens expirados |
| 5 | Uma tabela de consulta adulterada | O mapeamento é a fonte da verdade. Acesso de escrita via SQL injection ou credenciais vazadas redireciona um token para outro usuário | Proteja o banco de dados e as ferramentas administrativas com o mesmo cuidado que a aplicação |
O problema 5 é o que vale a pena examinar com calma. Nada muda no token; o que muda é o que ele significa. Isso é elevação de privilégio conseguida sem sequer tocar na credencial.
O token está bem. O trabalho todo está no que existe ao redor dele.
Coloque em prática
Uma API emite tokens assim:
const token = Math.random().toString(36).slice(2)
tokens[token] = { userId: 4821 }
res.json({ token })E os verifica assim:
const token = req.headers.authorization?.replace('Bearer ', '')
const entry = tokens[token]
if (!entry) return res.status(401).json({ error: 'Unauthorized' })
req.userId = entry.userIdEncontre quatro problemas.
Compare suas respostas
1. Math.random não é criptograficamente seguro. Sua saída é previsível a partir de valores anteriores, então um atacante que coleta alguns tokens consegue deduzir outros. crypto.randomBytes(32).toString('hex') é a correção, e só esse problema já torna todo o esquema falsificável.
2. Nenhuma expiração em lugar nenhum. Nada é armazenado sobre quando o token foi emitido e nada verifica isso na entrada, então todo token emitido é válido para sempre. Armazene uma expiração e verifique-a em toda requisição.
3. Os tokens são armazenados em texto puro. tokens[token] guarda a string original, então quem ler esse armazenamento consegue usar todo token nele. Armazene um hash e faça a consulta aplicando hash ao que chega.
4. tokens é um objeto simples, então a consulta é permeável. tokens['toString'] retorna Object.prototype.toString, um valor verdadeiro (truthy), então uma requisição trazendo o token literal toString passa direto pela verificação if (!entry).
entry.userId então é undefined. Se isso vira uma falha ou uma requisição autenticada como usuário undefined depende do código que vem depois. Use um Map, Object.create(null) ou Object.hasOwn().
O número 4 é o que parece inofensivo à primeira vista. Os outros três são coisas para lembrar; esse é uma propriedade dos objetos JavaScript que já causou problemas em sistemas reais.
Para onde isso vai a seguir
A tabela de consulta é o que torna esse modelo viável e também o que o limita. Ela te dá revogação, e significa que toda requisição toca um armazenamento compartilhado, exatamente o que a identidade sem estado deveria evitar.
Os JSON Web Tokens levam essa ideia até o fim removendo a consulta por completo, colocando a identidade dentro do próprio token.

