Skip to content
This page has been auto-translated and may contain errors.View in English

JSON Web Tokens ​

Um token opaco exige uma consulta: a string não significa nada, então o servidor precisa perguntar ao seu próprio armazenamento a quem ela pertence.

Tire a consulta e alguma coisa precisa substituí-la. Um JSON Web Token, ou JWT, faz isso colocando a resposta dentro do próprio token.

Três partes, uma string separada por pontos ​

Um JWT parece um amontoado de caracteres aleatórios, mas tem uma estrutura rígida. Três partes, unidas por pontos:

text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjQ4MjEsInJvbGUiOiJ0ZWFjaGVyIn0.4pcPyMD09olPSyXnrXCjTwXyr4BsezdI1AVTmud2fU4
└────────── header ──────────┘ └────────── payload ─────────┘ └────────── signature ──────────┘
  • Header. Qual algoritmo assinou o token e que se trata de um JWT.
  • Payload. As informações de identidade, chamadas de claims: quem é o usuário, qual função ele exerce, quando o token expira, quem o emitiu.
  • Signature. Um carimbo criptográfico que comprova que nada foi alterado.

O servidor o monta pegando o header e o payload, combinando-os com um segredo que só ele conhece, e produzindo a signature. As três partes são então codificadas e unidas.

Qualquer pessoa pode ler um JWT. Só quem tem o segredo consegue criar um que o servidor vá aceitar.

É isso que o torna confiável sem precisar de uma consulta, e é essa a ideia central resumida em uma frase.

JunoTrês partes, uma string separada por pontos Um selo de cera em um envelope é uma boa comparação. Qualquer um pode olhar para o selo, mas só quem tem o carimbo consegue fazer um novo.

Ou seja, o selo não esconde a carta. Ele prova quem a fechou, e que ninguém a abriu depois disso.

JunoTrês partes, uma string separada por pontos Cole um token de verdade em um decodificador e você verá o header e o payload em JSON puro na hora, sem precisar do segredo. Faça isso pelo menos uma vez com um token de algo que você esteja construindo; isso ajuda a próxima seção fazer mais sentido.

A signature é a parte que resiste. Ela só te diz sim ou não, e apenas se você tiver o segredo.

JunoTrês partes, uma string separada por pontos O fato de o header ser visível e manipulável pelo atacante é a origem da falsificação clássica de JWT. Basta definir alg como none, remover a signature, e qualquer servidor que decodifique o token em vez de verificá-lo vai aceitar o payload que você escolheu.

Isso já foi demonstrado contra um middleware jwt.decode, e o resultado é uma sessão válida como quem quer que você tenha alegado ser.

A correção é fixar o que você aceita em jwt.verify: especifique os algoritmos explicitamente, e fixe também issuer e audience. Nunca deixe o token dizer como ele deve ser verificado.

Uma unidade para lembrar enquanto você mexe nisso: clockTolerance é em segundos, não em milissegundos.

O fluxo, e o que mudou ​

  1. O usuário faz login com um nome de usuário e senha.
  2. O servidor os verifica.
  3. Em caso de sucesso, o servidor cria um JWT e o assina.
  4. O token vai para o cliente, que o armazena.
  5. Toda requisição seguinte o carrega no header Authorization, exatamente como um token opaco faria.
  6. O servidor verifica a signature e lê os claims. Sem consulta, sem armazenamento.

O passo seis é o único que difere dos tokens opacos do tipo bearer, e isso muda completamente o modelo:

Token opaco bearerJWT
ConteúdoNada, uma string aleatóriaClaims codificados: id do usuário, função, expiração
Para identificar o usuárioConsultarVerificar a signature, ler os claims
Armazenamento no servidorUma pequena tabela token-usuárioNenhum
Onde a identidade ficaNo servidorDentro do token

Um token opaco aponta para a identidade mantida no servidor. Um JWT contém essa identidade.

JunoO fluxo, e o que mudou Só um passo mudou, e é ele que decide tudo o mais.

Fazer uma consulta significa que o servidor pode verificar a resposta mais atual. Ler o token significa que o servidor recebe a resposta que era verdadeira no momento em que o token foi criado.

JunoO fluxo, e o que mudou Sem armazenamento significa nenhum repositório de sessão compartilhado para manter, nenhuma coordenação entre instâncias, e escalabilidade horizontal de graça. É uma vantagem operacional real, e é por isso que os JWTs costumam ser a escolha padrão para microsserviços.

O custo aparece no dia em que você precisar encerrar uma sessão imediatamente. Não existe registro para excluir, então o token continua funcionando até expirar. Planeje isso com antecedência, não no meio de um incidente.

JunoO fluxo, e o que mudou As funções dentro do token são o ponto mais delicado na prática. Rebaixe um admin e o token já emitido para ele ainda vai dizer admin até expirar, então sua mudança de permissão tem um atraso medido pelo tempo de vida que você escolheu.

Há duas saídas, e cada uma tem seu custo. Manter os tokens com vida muito curta, na casa dos minutos, e renová-los com frequência, o que reintroduz uma consulta a cada renovação. Ou manter uma lista de permissão ou de bloqueio dos ids dos tokens, o que reintroduz a consulta que você havia eliminado.

Vale ser honesto: as duas opções são o modelo stateless recomprando um pouco de estado. É um design perfeitamente válido, mas não é a vantagem gratuita que o discurso de venda promete.

Quatro formas de dar errado ​

Um JWT ainda é um token bearer, então toda armadilha dos tokens opacos continua valendo: roube-o e você vira o usuário. Ser autocontido acrescenta mais quatro.

O que dá erradoPor que isso importaA correção
1Presumir que está criptografadoEle está codificado, não criptografado. Quem quer que o tenha em mãos pode ler o payload. Divulgação de informação no STRIDE, exposição de dados sensíveis no OWASPNão coloque no payload nada que você não escreveria em um cartão-postal
2Um segredo fraco ou vazadoAdivinhar ou roubar o segredo permite que um atacante assine seus próprios tokens, incluindo um que diga admin. Elevação de privilégioUm segredo longo e aleatório, carregado do ambiente, nunca versionado
3Um payload sobrecarregadoTokens grandes significam headers grandes em toda requisição: requisições lentas, proxies insatisfeitos, logs inchados. E tudo que está ali vaza se o token vazarSó o que o servidor precisa em toda requisição
4Sem expiraçãoVocê não consegue revogar um JWT, então sem uma expiração ele funciona para sempre. Falha de autenticaçãoSempre defina uma, e mantenha-a curta

A armadilha 2 é a que transforma um pequeno erro em um comprometimento total. Um id de sessão vazado compromete uma conta; um segredo de assinatura vazado compromete todas as contas, inclusive as que ainda nem existem.

JunoQuatro formas de dar errado A armadilha um pega quase todo mundo, porque um JWT realmente parece um amontoado de caracteres sem sentido.

Mas não é embaralhado. Está escrito em um alfabeto que não é amigável para leitura, e qualquer decodificador o transforma de volta em texto simples na hora.

JunoQuatro formas de dar errado O segredo pertence a uma variável de ambiente, fora do repositório, e o "depois a gente rotaciona" precisa de um plano, porque rotacionar invalida todos os tokens ativos de uma vez.

Suportar dois segredos válidos durante uma janela de rotação é o que torna isso viável: verifique contra os dois, assine com o novo, e depois aposente o antigo.

JunoQuatro formas de dar errado A escolha do algoritmo decide quem pode emitir tokens. HS256 é simétrico, então o mesmo segredo assina e verifica, e todo serviço que confere um token também consegue criar um. RS256 é assimétrico: um serviço guarda a chave privada e assina, todos os outros verificam com a chave pública.

Para uma única aplicação, HS256 serve bem. Entre serviços diferentes, a assinatura simétrica acaba dando, sem alarde, a todo verificador o poder de forjar tokens, o que raramente é o que se pretendia.

Também vale saber que um JWT não precisa ser um token de acesso. É um formato de container, e usá-lo como link de redefinição de senha ou como token de verificação de e-mail é comum e faz sentido.

Cada um deles precisa de um claim de propósito, para que um token emitido para uma finalidade não possa ser apresentado para outra.

Mão na massa ​

Uma auditoria de segurança stateless, quatro cenários.

js
// 1. Um registro na tabela de consulta de tokens
const tokenTable = [
  { token: 'a91f...', userId: 4821, expires: '3000-01-01T00:00:00Z' },
]

// 2. Chamada em toda requisição
function authenticate(token) {
  const record = tokenTable.find((r) => r.token === token)
  return record ?? null
}

// 3. O payload que vai dentro de um JWT
{ id: 4821, name: 'Mara', admin: false, streetAddress: 'Rua Augusta, 1200',
  phone: '+55 11 91234-5678', tabSwitches: 14, windowResizes: 3 }

// 4. Configuração do servidor usada ao emitir JWTs
const config = { secret: 'secret123', algorithm: 'HS256' }
Compare suas respostas
#ArmadilhaCorreção
1Token de vida longa. Expira no ano 3000, quase certamente um erro de digitação, e continua sendo uma credencial válida por um milênioUma expiração de 30 minutos
2Sem revogação. A função encontra o registro e o retorna sem nunca olhar para expires, então um token expirado autentica normalmenteCompare expires com o momento atual, retorne null se já passou, e aproveite para excluir a linha
3Payload sobrecarregado, e divulgação de informação. Um endereço e um número de telefone ficam em algo que qualquer portador pode ler, junto com dados analíticos que o servidor nunca precisaid, name e admin, nada além disso
4Segredo fraco, e sem expiração. secret123 é adivinhável e está fixo no código, ou seja, está no repositórioUm segredo longo e aleatório vindo de uma variável de ambiente, além de um claim de expiração

O cenário 2 é o que se esconde melhor. A função parece correta: ela realmente encontra o token e realmente retorna null para um que não existe. Armazenar uma expiração e nunca checá-la equivale a não ter expiração nenhuma.

Para onde isso vai a seguir ​

Dois modelos stateless, um trocando uma consulta pela revogação e o outro trocando a revogação pela escala. Nos dois, sua aplicação continua responsável pelas senhas, pelas redefinições e por todo o aparato de provar quem alguém é.

OAuth e identidade delegada questiona se você sequer quer essa responsabilidade.