Node.js Password Hashing

Websites must never store passwords as plain text. A data leak would expose every account at once. Hashing turns each password into a scrambled code that nobody can reverse. This lesson shows how to hash passwords with the bcrypt library.

The Problem with Plain Text

Imagine a hotel that writes every guest's room key code on a public board. Anyone who reads the board enters every room. A database with plain passwords behaves like that board.

Plain storage                 Hashed storage
+---------+-------------+     +---------+--------------------------+
| user    | password    |     | user    | password_hash            |
+---------+-------------+     +---------+--------------------------+
| asha    | mypass123   |     | asha    | $2b$10$k8Zq...Xe1Lm      |
+---------+-------------+     +---------+--------------------------+
 Leak = everyone exposed       Leak = codes stay useless

Hashing and Encryption Differ

  • Encryption locks data, and a key unlocks it.
  • Hashing works in one direction only. No key exists to reverse it.

A login system never needs the original password. It only needs to compare a new hash with the stored hash.

Why Plain SHA-256 Falls Short

Fast hash functions help attackers. A modern computer tests billions of guesses every second. Password hashing needs a slow function with a built-in random value called a salt.

What a Salt Does

A salt is a random string added to the password before hashing. Two users with the same password receive different hashes. Attackers cannot use prepared tables of common password hashes.

"mypass123" + salt A --> hash 1
"mypass123" + salt B --> hash 2   (different result)

Install bcrypt

npm install bcrypt

Hash a Password

const bcrypt = require("bcrypt");

async function createHash(password) {
  const rounds = 10;
  return await bcrypt.hash(password, rounds);
}

createHash("mypass123").then(console.log);
// $2b$10$k8Zq...Xe1Lm

The number 10 is the cost factor. Each extra point doubles the work. A value between 10 and 12 suits most apps.

Read the Hash

$2b$10$k8ZqExampleSaltValueHereXe1LmHashedPasswordPart
 |  |  |_________________|_____________________________
 |  |   salt (22 chars)     hash
 |  cost factor
 algorithm version

The result stores the salt inside itself. You save one text value in the database.

Check a Password

async function verifyPassword(typed, savedHash) {
  const match = await bcrypt.compare(typed, savedHash);
  return match;  // true or false
}

The compare function reads the salt from the saved hash, hashes the typed password, and checks the result.

Sign-Up and Login in Express

const users = [];  // replace with a database

app.post("/signup", async (req, res) => {
  const { email, password } = req.body;
  if (!password || password.length < 8) {
    return res.status(400).send("Use at least 8 characters.");
  }
  const passwordHash = await bcrypt.hash(password, 10);
  users.push({ email, passwordHash });
  res.status(201).send("Account created.");
});

app.post("/login", async (req, res) => {
  const { email, password } = req.body;
  const user = users.find((u) => u.email === email);

  const ok = user && (await bcrypt.compare(password, user.passwordHash));
  if (!ok) {
    return res.status(401).send("Wrong email or password.");
  }
  res.send("Welcome back!");
});
SIGN-UP:  password --> bcrypt.hash --> save hash

LOGIN:    typed password --> bcrypt.compare --> saved hash
                                  |
                          true --> allow   false --> deny

Good Habits

  • Show one message for wrong email and wrong password. Separate messages reveal which emails exist.
  • Require a minimum length of eight characters or more.
  • Use only the async functions in servers. The sync versions block the event loop.
  • Never print passwords in logs.
  • Add rate limits to login routes to slow guessing attacks.
  • Serve the site over HTTPS so passwords travel safely.

Other Options

Argon2 and scrypt are strong alternatives. Node.js includes scrypt in the crypto module. Bcrypt remains a trusted, simple choice for beginners.

Practice Tasks

  • Hash the same password twice and compare the two results.
  • Write a function that returns true for the correct password and false for a wrong one.
  • Change the cost factor from 8 to 12 and measure the time difference.