Hashing and Salting passwords in Nodejs using bcrpyt !
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:
- 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:
- Install
bcrypt
livrary
npm install bcrypt
2. File structure
-backend
-index.js
- db.js
— routes
— — user.js
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
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.
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 !