Node.jsadvanced 10 min
Store passwords safely with hashing and salt, and issue signed tokens to prove who a user is.
How to hash passwords with scrypt and a salt, verify a login without ever storing the real password, and create and check a signed token.
Passwords leak. When a database is stolen, the damage depends on how the passwords were stored. Done right, attackers get useless scrambled values. Done wrong, every account is exposed, and people reuse passwords on other sites.
Authentication means proving who someone is. Authorisation is what they are allowed to do. This lesson covers authentication.
Rule one: never store a password. Store a hash: the output of a one-way function. You cannot turn a hash back into the password, but the same password always produces the same hash, so you can check a login by hashing what the user typed and comparing.
Two more ingredients:
scrypt, bcrypt or argon2. Fast hashes like plain SHA-256 let attackers guess billions of passwords per second. Node ships scrypt in node:crypto.Compare hashes with timingSafeEqual so the comparison time does not leak clues.
After login, the server gives the user a token to send with future requests. A simple signed token is a payload plus an HMAC signature made with a secret key only the server knows. If anyone changes the payload, the signature no longer matches. (Real projects often use JWTs or server-side sessions from a library.)
hashPassword creates a random 16-byte salt, hashes the password with scrypt, and stores salt:hash. The plain password is never saved.verifyPassword splits the stored value, hashes the typed password with the same salt, and compares the two results safely.true, the wrong one as false.true is printed for "different hash".signToken encodes the payload and signs it with a secret. Verification recomputes the signature and returns the payload when it matches: { userId: 7, role: 'student' }.role: "admin" without the secret cannot produce a valid signature, so verifyToken returns null.Run it on your computer. Change the secret in verifyToken only, and watch valid tokens start failing.
scrypt (or bcrypt or argon2) hash.timingSafeEqual; keep secrets in environment variables.You finished the Node.js track. Try the practice exercises to lock it in.
import { scryptSync, randomBytes, timingSafeEqual, createHmac } from "node:crypto";
function hashPassword(password) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(password, salt, 64).toString("hex");
return `${salt}:${hash}`;
}
function verifyPassword(password, stored) {
const [salt, hash] = stored.split(":");
const attempt = scryptSync(password, salt, 64);
return timingSafeEqual(attempt, Buffer.from(hash, "hex"));
}
const SECRET = "change-me-in-real-life";
function signToken(payload) {
const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
const sig = createHmac("sha256", SECRET).update(body).digest("base64url");
return `${body}.${sig}`;
}
function verifyToken(token) {
const [body, sig] = token.split(".");
const expected = createHmac("sha256", SECRET).update(body).digest("base64url");
if (sig !== expected) return null;
return JSON.parse(Buffer.from(body, "base64url").toString());
}
const stored = hashPassword("correct horse");
console.log("stored differs from password:", stored !== "correct horse");
console.log("right password:", verifyPassword("correct horse", stored));
console.log("wrong password:", verifyPassword("wrong horse", stored));
console.log("same password, different hash:", hashPassword("correct horse") !== stored);
const token = signToken({ userId: 7, role: "student" });
console.log("valid token:", verifyToken(token));
const tampered = signToken({ userId: 7, role: "student" }).replace(/^[^.]+/, Buffer.from(JSON.stringify({ userId: 7, role: "admin" })).toString("base64url"));
console.log("tampered token:", verifyToken(tampered));