Verificando uma senha
Parte da seção Autenticação e segurança do Journey de Node.js da Coddy. Lição 6 de 24.
Um login deriva uma chave a partir da senha tentada, usando o salt salvo e o comprimento original da chave. Um salt novo produziria uma chave diferente, mesmo para a senha correta. A senha original nunca é decodificada a partir do armazenamento.
// Um código de convite foi armazenado como salt.keyHex
const [salt, keyHex] = savedInvite.split('.');
const expected = Buffer.from(keyHex, 'hex');
const attempted = await scryptAsync(inviteCode, salt, 32);Compare as sequências de bytes resultantes usando timingSafeEqual, depois de confirmar que elas têm o mesmo comprimento. Isso evita uma saída antecipada dependente do conteúdo, que poderia revelar informações por meio do tempo de execução. === em Buffer verifica a identidade, e uma comparação normal de strings não oferece garantia de tempo constante. Sua função de senha usa o delimitador de dois-pontos do registro de senha e uma chave de 64 bytes. A rota POST /login que você adiciona usa o resultado para retornar 200 ou 401.
Desafio
FácilEsta etapa abre o projeto de API de anotações privadas preparado pelo curso, em vez dos arquivos do seu projeto de Never Store Passwords (incluindo passwords.js). Seu trabalho anterior continua na própria lição. Leia os arquivos fornecidos antes de editar passwords.js. Esta cópia contém funções auxiliares para hash de senhas e as rotas de depuração fornecidas.
Agora, o cadastro salva hashes com salt. Adicione um login que confira uma senha com o que foi salvo no cadastro.
- Em
passwords.js, adicione e exporte uma funçãoasyncverifyPassword(password, stored).storedé um valor salvo pelo cadastro, comof2bb...f065:c3f7...dbd2. Retornetruequandopasswordfor a senha que o gerou efalsecaso contrário:- Obtenha o salt que vem antes dos dois-pontos.
- Gere um hash de
passwordcom esse salt para obter uma chave de 64 bytes, da mesma forma quehashPasswordfaz. - Decodifique em hexadecimal a chave salva que vem depois dos dois-pontos. Se as duas chaves tiverem comprimentos diferentes em bytes, retorne
falsesem chamartimingSafeEqual, como em Comparando bytes de chave. Caso contrário, retorne o resultado da comparação.
- Em
server.js, importeverifyPasswordjunto comhashPassword. AdicionePOST /login. Ele recebe o mesmo corpo que o cadastro, por exemplo,{"username": "ada", "password": "correct horse"}.- Encontre o usuário com esse nome de usuário.
verifyPasswordé lenta e retorna uma promise, então aguarde sua conclusão. - Quando o usuário existir e a senha estiver correta, responda com o status 200 e
{"username": "ada"}. - Caso contrário, responda com o status 401 e
{"error": "Wrong username or password"}. 401 Unauthorized significa que a solicitação não comprovou quem está fazendo o pedido. - Envie exatamente a mesma resposta 401 para uma senha incorreta e para um nome de usuário que ninguém cadastrou. Assim, o corpo da resposta nunca informa a ninguém quais nomes de usuário existem.
- Encontre o usuário com esse nome de usuário.
Experimente: Pressione Executar. Envie POST /signup com {"username": "ada", "password": "correct horse"}: status 201. Envie POST /login com o mesmo corpo: status 200 e {"username": "ada"}. Envie POST /login com {"username": "ada", "password": "wrong horse"}: status 401 e {"error": "Wrong username or password"}. Envie POST /login com {"username": "nobody", "password": "correct horse"}: o mesmo status 401.
Experimente você mesmo
import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';
const scryptAsync = promisify(scrypt);
// Transforma uma senha no texto que signup armazena.
export async function hashPassword(password) {
const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(password, salt, 64);
return `${salt}:${key.toString('hex')}`;
}
Todas as lições de Autenticação e segurança
Pratique por conta própria: Playground de Express