Password Hashing With Salt Using Bcrypt In Cloudnotebook App

Posted on July 20, 2026 by Vishesh Namdev
Python C C++ Javascript React JS
Password Hashing with Salt using Bcrypt for our CloudNoteBook App | React JS Tutorial for Beginners

Password Hashing with Salt using Bcrypt for our CloudNoteBook App 🔐🔑 Right now, when a user signs up on CloudNoteBook, their password is stored directly in MongoDB as plain text. That's a serious security risk — if our database is ever leaked or accessed by someone with bad intentions, every single user's password is exposed as-is. In this tutorial, we'll fix that using bcrypt, an industry-standard hashing algorithm that scrambles passwords with a unique salt before they're ever saved.

In this tutorial, we will learn:

  • What password hashing is and why storing plain text passwords is dangerous
  • What a "salt" is and how it protects against rainbow table attacks
  • Installing and configuring bcryptjs in our Express backend
  • Hashing the password before saving a new user with bcrypt.hash
  • Comparing a hashed password on login with bcrypt.compare
  • Best practices for choosing salt rounds
  • ---

    What is Password Hashing?

    Hashing converts a readable password like MyPassword123 into a fixed-length, irreversible string of characters — something like $2a$10$N9qo8uLOickgx2Zm.... Unlike encryption, hashing cannot be reversed back into the original password. When a user logs in later, we don't "decrypt" the stored hash — instead we hash the entered password again and compare the two hashes.

    What is a Salt, and Why Does It Matter?

    A salt is a random string added to a password before it's hashed. Without a salt, two users with the same password (e.g. 123456) would end up with the exact same hash — making it easy for attackers to use precomputed "rainbow tables" to crack many passwords at once. Bcrypt automatically generates a unique salt for every password, so even identical passwords produce completely different hashes.

    ---

    Step 1: Install bcryptjs

    In your backend folder, install the bcryptjs package. It's a pure JavaScript implementation of bcrypt, so it works without any native build tools.

    npm install bcryptjs
    ---

    Step 2: Import bcryptjs in the Auth Routes

    Open your routes/auth.js file (or wherever your signup route lives) and import bcryptjs along with your existing User model.

    const express = require("express");
    const router = express.Router();
    const User = require("../models/User");
    const bcrypt = require("bcryptjs");
    const { body, validationResult } = require("express-validator");
    ---

    Step 3: Generate a Salt and Hash the Password

    Inside the create-user (signup) route, instead of saving req.body.password directly, we first generate a salt using bcrypt.genSalt, then hash the password with that salt using bcrypt.hash.

    // ROUTE 1: Create a User using: POST "/api/auth/createuser"
    router.post(
      "/createuser",
      [
        body("name", "Enter a valid name").isLength({ min: 3 }),
        body("email", "Enter a valid email").isEmail(),
        body("password", "Password must be at least 5 characters").isLength({ min: 5 }),
      ],
      async (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          return res.status(400).json({ errors: errors.array() });
        }
     
        try {
          let user = await User.findOne({ email: req.body.email });
          if (user) {
            return res.status(400).json({ error: "A user with this email already exists" });
          }
     
          // Generate a salt with 10 rounds
          const salt = await bcrypt.genSalt(10);
          // Hash the password using the generated salt
          const secPassword = await bcrypt.hash(req.body.password, salt);
     
          user = await User.create({
            name: req.body.name,
            email: req.body.email,
            password: secPassword,
          });
     
          res.json({ success: true, user: { id: user.id, name: user.name } });
        } catch (error) {
          console.error(error.message);
          res.status(500).send("Internal Server Error");
        }
      }
    );
    ---

    Step 4: Compare Passwords on Login

    When a user logs in, we never re-hash and compare strings manually. Instead, we use bcrypt.compare, which takes the plain text password entered and the stored hash, and returns true or false depending on whether they match.

    // ROUTE 2: Authenticate a User using: POST "/api/auth/login"
    router.post(
      "/login",
      [
        body("email", "Enter a valid email").isEmail(),
        body("password", "Password cannot be blank").exists(),
      ],
      async (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          return res.status(400).json({ errors: errors.array() });
        }
     
        const { email, password } = req.body;
        try {
          let user = await User.findOne({ email });
          if (!user) {
            return res.status(400).json({ error: "Please try to login with correct credentials" });
          }
     
          const passwordCompare = await bcrypt.compare(password, user.password);
          if (!passwordCompare) {
            return res.status(400).json({ error: "Please try to login with correct credentials" });
          }
     
          res.json({ success: true, message: "Login successful" });
        } catch (error) {
          console.error(error.message);
          res.status(500).send("Internal Server Error");
        }
      }
    );
    ---

    Step 5: Full Updated auth.js

    Here is the complete auth.js file with password hashing and comparison wired into the signup and login routes.

    const express = require("express");
    const router = express.Router();
    const User = require("../models/User");
    const bcrypt = require("bcryptjs");
    const { body, validationResult } = require("express-validator");
     
    // ROUTE 1: Create a User using: POST "/api/auth/createuser"
    router.post(
      "/createuser",
      [
        body("name", "Enter a valid name").isLength({ min: 3 }),
        body("email", "Enter a valid email").isEmail(),
        body("password", "Password must be at least 5 characters").isLength({ min: 5 }),
      ],
      async (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          return res.status(400).json({ errors: errors.array() });
        }
     
        try {
          let user = await User.findOne({ email: req.body.email });
          if (user) {
            return res.status(400).json({ error: "A user with this email already exists" });
          }
     
          const salt = await bcrypt.genSalt(10);
          const secPassword = await bcrypt.hash(req.body.password, salt);
     
          user = await User.create({
            name: req.body.name,
            email: req.body.email,
            password: secPassword,
          });
     
          res.json({ success: true, user: { id: user.id, name: user.name } });
        } catch (error) {
          console.error(error.message);
          res.status(500).send("Internal Server Error");
        }
      }
    );
     
    // ROUTE 2: Authenticate a User using: POST "/api/auth/login"
    router.post(
      "/login",
      [
        body("email", "Enter a valid email").isEmail(),
        body("password", "Password cannot be blank").exists(),
      ],
      async (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          return res.status(400).json({ errors: errors.array() });
        }
     
        const { email, password } = req.body;
        try {
          let user = await User.findOne({ email });
          if (!user) {
            return res.status(400).json({ error: "Please try to login with correct credentials" });
          }
     
          const passwordCompare = await bcrypt.compare(password, user.password);
          if (!passwordCompare) {
            return res.status(400).json({ error: "Please try to login with correct credentials" });
          }
     
          res.json({ success: true, message: "Login successful" });
        } catch (error) {
          console.error(error.message);
          res.status(500).send("Internal Server Error");
        }
      }
    );
     
    module.exports = router;
    ---

    Key Bcrypt Methods Explained

    Method Purpose Used In
    bcrypt.genSalt(rounds) Generates a random salt; higher rounds = more secure but slower Signup route, before hashing
    bcrypt.hash(password, salt) Combines the password and salt into a one-way hash Signup route, before saving the user
    bcrypt.compare(password, hash) Hashes the entered password and checks it against the stored hash Login route, to verify credentials
    ---

    Features and Learnings:-

  • Understood why storing plain text passwords is a critical security flaw.
  • Learned what a salt is and how it defeats rainbow table attacks.
  • Installed and configured bcryptjs in the Express backend.
  • Generated a salt and hashed the password using bcrypt.genSalt and bcrypt.hash.
  • Verified login credentials securely using bcrypt.compare.
  • Understood the trade-off between salt rounds and performance.
  • Prepared CloudNoteBook's authentication system for the next step: generating JWT tokens on signup and login.
  • 📢 Important Note 📢

    How did you feel about this post?

    😍 🙂 😐 😕 😡

    Was this helpful?

    👍 👎