Aprenda / Segurança web

FundamentosAula 5 de 1618 min0% da trilha

JWT: o token que carrega a própria assinatura

Um JWT não esconde nada de quem o lê — ele só prova que ninguém alterou o que está escrito ali.

JWT (JSON Web Token) é o formato mais comum de bearer token hoje. A diferença dele pra um token aleatório qualquer (como o ID de sessão da lição 2) é que um JWT carrega dados legíveis dentro de si mesmo — quem é o usuário, quando o token expira — e uma assinatura que prova que ninguém alterou esses dados no caminho. Isso muda completamente como ele deve ser tratado, e onde ele quebra.

As três partes, separadas por ponto

um JWT real, separado nas 3 partes
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoidXN1YXJpbyIsImV4cCI6MTc1ODIwMDAwMH0.k3f9A1z...

╰──────── header ────────╯╰──────────── payload ────────────╯╰── assinatura ──╯
header e payload, decodificados
// header — qual algoritmo foi usado pra assinar
{ "alg": "HS256" }

// payload — os dados de fato ("claims")
{ "sub": "123", "role": "usuario", "exp": 1758200000 }

header e payload são só JSON convertido pra Base64 — não são criptografados. A terceira parte, a assinatura, é o resultado de rodar um algoritmo (HMAC-SHA256, no exemplo acima) sobre o header e o payload combinados, usando uma chave secreta que só o servidor conhece. É essa assinatura que garante que, se um byte do payload mudar, o resultado do algoritmo muda junto — e deixa de bater com a assinatura original.

"Assinado, não criptografado" — o que isso muda na prática

Qualquer pessoa com o token em mãos consegue decodificar o header e o payload sozinha, sem chave nenhuma — é só Base64, o mesmo que colar num decodificador online. O que ninguém sem a chave secreta consegue fazer é gerar uma assinatura válida pra um payload alterado. É por isso que um JWT nunca deve carregar dado sensível no payload (senha, número de cartão) — ele não está escondido, só está assinado.

Payload adulterado
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.k3f9A1z...
// alguém editou "role":"usuario" pra "role":"admin" no payload,
// mas manteve a MESMA assinatura antiga (não tem como recalcular
// sem a chave secreta)

// servidor recalcula o HMAC do novo payload e compara:
// resultado NÃO bate com "k3f9A1z..." → 401, token rejeitado
Payload original
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoidXN1YXJpbyJ9.k3f9A1z...
// payload intacto, exatamente como o servidor emitiu

// servidor recalcula o HMAC e compara:
// resultado bate com "k3f9A1z..." → token aceito

Note que o atacante do exemplo "ruim" nem precisa entender criptografia pra tentar o ataque — o Base64 é trivial de editar. O que impede o ataque é só uma coisa: ele não tem a chave secreta pra gerar uma nova assinatura válida pro payload que ele acabou de alterar.

exp — o token tem prazo de validade embutido

O campo exp ("expiration") guarda um timestamp; o servidor confere esse valor a cada requisição e rejeita qualquer token com exp no passado, mesmo que a assinatura continue perfeitamente válida. É comum ver tokens de acesso com vida curta (minutos a poucas horas) acompanhados de um segundo token, o refresh token, guardado de forma mais protegida e usado só pra pedir um token de acesso novo sem repetir login inteiro — mas o mecanismo de expiração em si é sempre o mesmo campo exp.

Por que revogar um JWT antes do exp é difícil

Aqui está a diferença mais importante em relação à sessão por cookie da lição 2. Lá, "derrubar uma sessão" era apagar uma linha numa tabela — o servidor consultava aquela tabela a cada requisição de qualquer jeito. Um JWT foi desenhado pra ser stateless: o servidor não guarda uma lista de "tokens ativos" pra consultar, ele só reconfere a assinatura matematicamente a cada requisição. Isso é ótimo pra escalar (nenhum banco é consultado só pra validar identidade) — mas significa que não existe um "apagar esse token" de verdade antes do exp chegar. Se um JWT vazar, ele continua válido até expirar sozinho, a não ser que a aplicação implemente uma lista de revogação separada (voltando a depender de uma consulta central, o que anula parte do ganho de ser stateless).

Exercício

Um time decide guardar { "saldo_conta": 15000 } dentro do payload de um JWT, pra evitar uma consulta ao banco a cada requisição. Explique por que isso é perigoso, mesmo o token estando assinado corretamente.

Quiz rápido

Por que um JWT é descrito como 'assinado, não criptografado'?
Por que revogar um JWT antes do seu exp expirar é mais difícil do que invalidar uma sessão por cookie?