Autenticar é responder "quem é você?"
A autenticação verifica a identidade de quem faz a requisição. Quase sempre começa por um par usuário/senha e termina em um token que o cliente envia em cada requisição posterior.
Nunca guarde senhas em texto puro
Se o seu banco de dados guarda "hunter2" e alguém o rouba, já tem todas as
senhas dos seus usuários (e, como as pessoas as reutilizam, também as do e-mail e
do banco delas). A regra é absoluta: a senha jamais é guardada, guarda-se o
seu hash.
Um hash é o resultado de uma função de mão única: é fácil calcular
hash("hunter2"), mas inviável recuperar "hunter2" a partir do hash.
Ao fazer login, você re-hasheia o que o usuário digita e compara os hashes.
O hash precisa de "salt"
Se todos os hashes usam a mesma função sem mais nada, duas pessoas com a mesma senha terão o mesmo hash, e um atacante pode pré-calcular tabelas (rainbow tables) com os hashes de milhões de senhas comuns.
O salt é um valor aleatório, diferente para cada usuário, que é misturado com a senha antes do hashing. Assim, duas senhas iguais produzem hashes diferentes, e as tabelas pré-calculadas deixam de servir.
// Conceitual com bcrypt (NÃO está neste sandbox):
import bcrypt from "bcrypt";
// No cadastro: gera salt + hash (o bcrypt o inclui dentro do hash).
const hash = await bcrypt.hash(senha, 10); // 10 = custo/rodadas
// No login: compara sem "des-hashear" nada.
const ok = await bcrypt.compare(senhaDigitada, hash);
O bcrypt é lento de propósito (o "custo" controla quantas rodadas). Essa lentidão é desejável: ela freia os ataques de força bruta.
Nos exercícios não temos bcrypt no sandbox, então vamos simular um hash determinístico simples. A ideia (mão única + salt) é a mesma.
Tokens JWT
Depois de validar a senha, você não quer pedi-la em cada requisição. Você emite um token: uma credencial assinada que o cliente guarda e reenvia.
Um JWT (JSON Web Token) tem três partes separadas por pontos:
cabecalho.payload.assinatura
- Cabeçalho: o algoritmo de assinatura (p. ex.
HS256). - Payload: os dados (claims):
{ sub: "u1", papel: "admin", exp: ... }. - Assinatura: calculada com uma chave secreta que só o servidor conhece.
Cabeçalho e payload vão em Base64 (legíveis por qualquer um: não cifre nada sensível ali). O que protege o token é a assinatura: se alguém adulterar o payload, a assinatura deixa de bater e o servidor rejeita o token.
// Conceitual com jsonwebtoken (NÃO está neste sandbox):
import jwt from "jsonwebtoken";
const token = jwt.sign({ sub: usuario.id, papel: usuario.papel }, SEGREDO, {
expiresIn: "1h",
});
const payload = jwt.verify(token, SEGREDO); // lança se a assinatura for inválida
Nos exercícios vamos simular a assinatura com uma função
assinar(texto, segredo)determinística (em produção seria HMAC-SHA256). O token écorpo.assinatura; ao verificar, a assinatura é recalculada e, se o payload foi adulterado, ela deixa de bater e é rejeitado. Assim você pratica a garantia de integridade real, não apenas decodificar Base64.
O fluxo completo
POST /logincom usuário e senha.- O servidor valida a senha contra o hash guardado.
- Se estiver correta, assina um token e o devolve.
- O cliente o guarda e, em cada requisição, o manda no cabeçalho:
Authorization: Bearer <token>
- O servidor verifica o token em cada requisição e sabe quem pede o quê.
Exemplos
Token assinado: alterar o payload invalida a assinatura
// "Assinatura" didática determinística (em produção: HMAC-SHA256).
function assinar(texto, segredo) {
let h = 0;
for (const c of texto + "|" + segredo) h = (Math.imul(h, 31) + c.charCodeAt(0)) | 0;
return (h >>> 0).toString(16);
}
function criarToken(payload, segredo) {
const corpo = btoa(JSON.stringify(payload));
return corpo + "." + assinar(corpo, segredo);
}
function verificarToken(token, segredo) {
const [corpo, assinatura] = token.split(".");
if (!corpo || assinatura !== assinar(corpo, segredo)) return null; // adulterado
return JSON.parse(atob(corpo));
}
const token = criarToken({ sub: "u1", papel: "user" }, "s3cr3t");
console.log(verificarToken(token, "s3cr3t").papel); // "user"
// Um atacante muda o payload, mas sem o segredo não consegue re-assinar:
const [, assinatura] = token.split(".");
const falso = btoa(JSON.stringify({ sub: "u1", papel: "admin" })) + "." + assinatura;
console.log(verificarToken(falso, "s3cr3t")); // null (rejeitado)