Facebook Pixel

How to Hash Passwords in Node.js With bcrypt

Hashing passwords is non-negotiable. Here is how to do it with bcrypt in Node.js.

How to Hash Passwords in Node.js With bcrypt

Storing passwords in plain text is a disaster. Hashing is non-negotiable. Here is how to do it with bcrypt in Node.js.

Why Hash Passwords

If your database is leaked, plain-text passwords expose every user. People reuse passwords, so one leak breaks their other accounts too. Hashing turns passwords into irreversible strings. Even if the database is leaked, attackers cannot recover the passwords.

Why bcrypt

bcrypt is a password hashing function designed for passwords. It is slow on purpose (so brute-forcing hashes is expensive) and has a salt built in (so two users with the same password get different hashes).

Install

npm install bcrypt.

Hash a Password

const bcrypt = require('bcrypt');
const hashed = await bcrypt.hash(password, 10);

The second argument is the salt rounds. 10 is a good default. Higher is slower (and safer); lower is faster (and weaker). 10 takes about 100ms on a modern CPU.

Verify a Password

const match = await bcrypt.compare(password, user.passwordHash);
if (!match) return res.status(401).json({ error: 'Invalid credentials' });

bcrypt.compare handles the salt extraction. You do not need to store the salt separately.

Where to Hash

Hash in a pre('save') hook in Mongoose, or in the controller before calling User.create:

userSchema.pre('save', async function(next) {
  if (!this.isModified('passwordHash')) return next();
  this.passwordHash = await bcrypt.hash(this.passwordHash, 10);
  next();
});

Hash only when the password has changed. Skip otherwise (profile updates should not rehash).

Handling Updates

For findByIdAndUpdate, hash in a pre('findOneAndUpdate') hook:

userSchema.pre('findOneAndUpdate', async function(next) {
  const update = this.getUpdate();
  if (update.passwordHash) {
    update.passwordHash = await bcrypt.hash(update.passwordHash, 10);
  }
  next();
});

Mark the Field With select: false

passwordHash: { type: String, required: true, select: false }

Excluded from find queries by default. Use .select('+passwordHash') only when needed (e.g., for login).

Common Mistakes

  • Hashing sync in request handlers: blocks the event loop. Use async APIs.
  • Using a low salt rounds (e.g., 5): too fast to brute-force. Use 10+.
  • Storing the salt separately: bcrypt stores it in the hash. You do not need a separate column.
  • Logging passwords or hashes: never log either. Filter them from logs.
  • Returning passwordHash in responses: use select: false and a serializer.

The Takeaway

Hash passwords with bcrypt using bcrypt.hash(password, 10) and verify with bcrypt.compare(password, hash). Use 10 salt rounds. Hash in a pre('save') hook. Mark the field with select: false. Never log passwords or hashes. Never return passwordHash in responses.

Use bcrypt. const hashed = await bcrypt.hash(password, 10). The second argument is salt rounds; 10 is a good default. Verify with bcrypt.compare(password, hash). Hash in a pre('save') Mongoose hook or in the controller.

bcrypt is designed for passwords. It is slow on purpose (so brute-forcing hashes is expensive) and has a salt built in (so two users with the same password get different hashes). It is the standard choice.

10 is a good default. It takes about 100ms on a modern CPU. Higher is slower (safer against brute force); lower is faster (weaker). For high-security apps, use 12. For most apps, 10 is fine.

No. bcrypt stores the salt inside the hash. You only store the hash. bcrypt.compare extracts the salt automatically when verifying.

To exclude it from find queries by default. Without this, User.find() returns passwordHash to the client. Use .select('+passwordHash') only when needed (e.g., for login).

Ready to master Node.js completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.