Hashing and Salting passwords in Nodejs using bcrpyt !

Sanket Jagdale
5 min readJan 30, 2024

--

Hashing and Salting passwords

Hashing and salting passwords is important for security reasons. When users create accounts on websites, their passwords should be stored securely to prevent unauthorized access in case the database is compromised. Here’s why hashing and salting are crucial:

  1. Password Hashing:
  • Security: Hashing is a one-way process, making it difficult for attackers to reverse-engineer the original password from the hash.
  • Consistency: Hashing ensures that the stored password is consistently represented in a fixed-size hash format, regardless of the length or complexity of the original password.

2. Salting:

  • Uniqueness: Salting involves adding a random value (salt) to each password before hashing. This ensures that even if two users have the same password, their hashes will be different due to the unique salt.
  • Defense Against Attacks: Salting helps defend against precomputed attacks, rainbow table attacks, and other types of attacks that attempt to guess passwords by using precomputed hashes.

Here’s how you can hash and salt passwords in Node.js with Express using the bcrypt library:

  1. Install bcrypt livrary

npm install bcrypt

2. File structure

-backend
-index.js
- db.js
— routes
— — user.js

File system

3. Connect express and mongoose and create userSchema

const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const MONGO_URL = process.env.MONGO_URL;
mongoose.connect(MONGO_URL);


const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true,
minLength: 3,
maxLength: 30,
},
password: {
type: String,
required: true,
minLength: 6,
},
firstName: {
type: String,
required: true,
trim: true,
maxLength: 50,
},
lastName: {
type: String,
required: true,
trim: true,
maxLength: 50,
},
});


const User = mongoose.model("User", userSchema);

module.exports = {
User,
};

4. In db.js , Write createHash and validatePassword for generating hash from password and validate it.


const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true,
minLength: 3,
maxLength: 30,
},
password: {
type: String,
required: true,
minLength: 6,
},
firstName: {
type: String,
required: true,
trim: true,
maxLength: 50,
},
lastName: {
type: String,
required: true,
trim: true,
maxLength: 50,
},
});


// -------------------------- bcrypt methods ---------------------------- //

//* Method to generate a hash from plain text

userSchema.methods.createHash = async function (plainTextPassword) {

// Hashing user's salt and password with 10 iterations,
const saltRounds = 10;

// create salt and hash
return await bcrypt.hash(plainTextPassword, saltRounds);
};

//* Validating the candidate password with stored hash and hash function

userSchema.methods.validatePassword = async function (candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};

// -------------------------- bcrypt methods ---------------------------- //

const User = mongoose.model("User", userSchema);

module.exports = {
User,
};

5. In routes/user.js define signin and signup routes

const express = require("express");
const zod = require("zod");
const { User } = require("../db.js");
const jwt = require("jsonwebtoken");

const JWT_SECRET = process.env.JWT_SECRET;

const router = express.Router();

const signupBody = zod.object({
username: zod.string().email(),
firstName: zod.string(),
lastName: zod.string(),
password: zod.string(),
});

//* Route : .../api/v1/user/signup
router.post("/signup", async (req, res) => {
try {
const { success } = signupBody.safeParse(req.body);

if (!success) {
return res.status(411).json({
message: "Invalid Inputs ! ",
});
}

const existingUser = await User.findOne({
username: req.body.username,
});

if (existingUser) {
return res.status(411).json({
message: "User already exists !",
});
}

const newUser = new User({
username: req.body.username,
password: req.body.password,
firstName: req.body.firstName,
lastName: req.body.lastName,
});

var hashedPassword = await newUser.createHash(newUser.password);
newUser.password = hashedPassword;

await newUser.save();

const userId = newUser._id;

const token = jwt.sign(
{
userId,
},
JWT_SECRET
);

res.json({
message: "User created successfully",
token: token,
});
} catch (error) {
console.error(error);
res.status(500).json({
message: "Internal Server Error",
});
}
});

//?----------------------------------------------?//

const signinBody = zod.object({
username: zod.string().email(),
password: zod.string(),
});

//* Route : .../api/v1/user/signin

router.post("/signin", async (req, res) => {
const { success } = signinBody.safeParse(req.body);

if (!success) {
return res.status(411).json({
message: "Username Already taken / Invalid Inputs !",
});
}

const user = await User.findOne({
username: req.body.username,
});

if (user) {
if (await user.validatePassword(req.body.password)) {
const token = jwt.sign(
{
userId: user._id,
},
JWT_SECRET
);

res.json({
token: token,
});
return;
} else {
return res.status(400).json({
message: "Incorrect Password",
});
}
}
});

module.exports = router;

Once we write the code, we can test it using Postman or VS code extension called thunderclient . Lets test this out !

Start the server using npx nodemon index.js

server started

Lets first hit signnup route !

Lets give some inputs in body !

Here you can see the new user and respective JWT token is created. Also the new entry is added into the database.

DB entry

The thing to notie here is, the stored password is hashed !

Now lets test out the signin route !

So this is how you can easily hash your passwords using bcrypt !

If you liked this blog, Do follow Sanket Jagdale !

--

--

Sanket Jagdale
Sanket Jagdale

Written by Sanket Jagdale

Full stack web developer and content creator with a focus on delivering high-quality content and my experiences to the developers community.

No responses yet