Node.js cookbook · Intermediate
Hash passwords and implement login safely in Node.js
Store user passwords securely and build a login endpoint resistant to brute force and enumeration.
Approach
Hash with Argon2id (argon2 package) or bcrypt, verify on login, rehash when parameters change, return the same error for unknown users and wrong passwords (and verify against a dummy hash to equalize timing), rate-limit attempts, and rotate the session on login.
Solution
import argon2 from "argon2";
const HASH_OPTIONS = { type: argon2.argon2id, memoryCost: 19_456, timeCost: 2, parallelism: 1 }; // OWASP baseline
const DUMMY_HASH = await argon2.hash("not-a-real-password", HASH_OPTIONS);
export async function register(email: string, password: string) {
if (password.length < 12) throw new HttpError(422, "Validation failed", "Password must be at least 12 characters");
const hash = await argon2.hash(password, HASH_OPTIONS);
return users.create({ email: email.toLowerCase(), passwordHash: hash });
}
app.post("/api/auth/login", loginLimiter, async (req, res) => {
const { email, password } = req.body as { email: string; password: string };
const user = await users.findByEmail(String(email).toLowerCase());
const ok = await argon2.verify(user?.passwordHash ?? DUMMY_HASH, String(password));
if (!user || !ok) return res.status(401).json({ title: "Invalid email or password" });
if (argon2.needsRehash(user.passwordHash, HASH_OPTIONS)) {
await users.updatePasswordHash(user.id, await argon2.hash(password, HASH_OPTIONS));
}
req.session.regenerate((err) => { // prevent session fixation
if (err) return res.status(500).end();
req.session.userId = user.id;
res.json({ id: user.id, email: user.email });
});
});
Pitfalls
- SHA-256/MD5 password hashes.
- Distinct errors that reveal registered emails.
- No rate limiting or lockout strategy.
Variations
- Offer passkeys (WebAuthn) via
@simplewebauthn/server. - Delegate auth to an identity provider (Auth0, Keycloak, Cognito).
Further reading: OWASP Password Storage Cheat Sheet
Reference: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
More Node.js recipes
- Add health and readiness endpoints to a Node.js service
- Add JWT authentication to an Express API
- Cache expensive lookups in Redis from Node.js
- Call external APIs with timeouts and retries in Node.js
- Centralize error handling in Express 5
- Implement cursor pagination with PostgreSQL in Node.js
- Offload CPU-heavy work with worker threads
- Process background jobs with BullMQ