O problema: o HTTP não lembra
Cada requisição HTTP é independente: o servidor não sabe, por si só, que "esta requisição é da mesma pessoa que fez login há um minuto". Precisamos de uma credencial que o cliente apresente em cada chamada. Há duas grandes famílias.
Opção A: sessões com cookies
- O usuário faz login. O backend cria uma sessão do seu lado (em memória, Redis ou um banco de dados) e gera um id de sessão opaco.
- Esse id viaja até o navegador dentro de um cookie. A partir daí, o navegador o anexa automaticamente em cada requisição ao mesmo domínio.
- O backend recebe o cookie, busca a sessão pelo seu id e sabe quem você é.
O estado vive no servidor; o cookie só guarda um ponteiro para ele.
Opção B: tokens (JWT)
- Após o login, o backend assina um token (um JWT) que já contém os dados do usuário e o devolve no corpo da resposta.
- O frontend o guarda (tipicamente no
localStorage) e o anexa à mão em cada requisição, no cabeçalhoAuthorization: Bearer <token>. - O backend verifica a assinatura do token e confia no seu conteúdo sem consultar nenhum armazenamento: o estado viaja no próprio token (é stateless).
Onde se guarda e quais riscos tem
| Armazenamento | Quem o anexa | Risco principal |
|---|---|---|
Cookie httpOnly |
O navegador, sozinho | CSRF |
localStorage |
Seu código JS | XSS |
- Um cookie marcado como
httpOnlynão é acessível pelo JavaScript: mesmo que haja uma falha de XSS (injeção de código), o atacante não consegue ler o token. Em contrapartida, como o navegador o envia sozinho, é vulnerável a CSRF (que um site malicioso provoque requisições à sua API em seu nome). - O
localStoragesim é acessível pelo JavaScript: se houver XSS, o atacante lê o token e se faz passar por você. Não sofre CSRF (porque é preciso anexá-lo à mão), mas o roubo por XSS é grave.
Anatomia de um JWT
Um JWT são três partes separadas por pontos, cada uma em base64url:
eyJhbG... . eyJzdWIiOiJhbmEi... . 3rXc8f...
cabeçalho payload (dados) assinatura
- Cabeçalho: o algoritmo de assinatura (p. ex.
HS256). - Payload: as claims (dados):
sub(sujeito),exp(validade), papel... - Assinatura: garante que o token não foi alterado.
⚠️ O payload está codificado, não criptografado: qualquer um pode lê-lo. A assinatura evita que o modifiquem, mas nunca coloque segredos dentro.
Exemplos
Ler o payload de um JWT sem verificar a assinatura
const jwt = "cabecalho." + btoa(JSON.stringify({ sub: "ana", papel: "admin" })) + ".assinatura";
const [, payloadB64] = jwt.split(".");
console.log(JSON.parse(atob(payloadB64)));