It is crucial to keep users’ passwords secure to protect against cyber attacks. The first step is storing passwords on a secure database server. However, no matter how many precautions you take, you can never assume the database is impenetrable. That is why you must take a second step to make deciphering any stolen passwords much harder: salting and hashing.
In this post we will:
- Learn about hashing in NodeJS
- Learn about salting
- Salt and hash password using bcrypt
- Compare the password with the hash
What is hashing?
Hashing refers to taking plain text and putting it through a hash algorithm. A hash algorithm takes input text of any size and returns a string of fixed length. No matter the size of string/text it always returns the same length string.
Because hash algorithms return the same hash strings it's most likely predictable & it is also possible that a hacker can use another string and it generates the same hash value. As we can see hashing is not enough.
What is salting?
Salting is nothing but adding a random string to your password hash. Now when you use salt, it automatically includes in your hashed password. So here for the same password overall hashed value is different and it's not predictable.
Setting up the required environments and libraries
This is a NodeJS project. It uses NPM to manage its dependencies. You need to create a new project directory and initialize the node app using:
npm init -y
It will generate the default package.json that will manage the dependencies for this project.
Now install packages that need for this project
npm init express bcrypt --save
Now let's install nodemon which is actually used as a code watcher(Which means if you do any changes in your project then your application will get restarted automatically)
Here is my complete package.json
{
"name": "auth",
"version": "1.0.0",
"description": "",
"scripts": {
"devStart":"nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.0.1",
"express": "^4.17.3"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
Here I have changed scripts & added devStart with the project main file.
Create NodeJS basic server
Now create a server.js file that we have used in the package.json and add the below code
const express = require('express');
const app = express();
app.listen(3000);
Here I have just created an express server app & listening to port 3000.
Now add bcrypt to our application which is actually used for cryptography purposes in NodeJS and mainly used to hash passwords.
We will be using an array for storage purposes. Please note for real applications you have to store data in some persistent storage. But for learning purposes, I'm storing it in Array.
Create a route to get user details
First of all, I will be creating a route to get all users. We do not create such a route in the production but here for learning purposes, we will be creating that
let users = []; // store user details in an arrayapp.get('/users', (req, res) => {
res.json(users)
});

Create a user registration route
Create a post route to register users. But before that, we will have to add a middleware to accept the JSON body. In NodeJS we can use body-parser but the new version of express also support JSON body with app.use(express.json()) middleware. I'm adding this middleware to our code
app.use(express.json());app.post('/user/register', async (req, res) => {
try {
const salt = await bcrypt.genSalt();
const passwordHash = await bcrypt.hash(req.body.password, salt);
users.push({username: req.body.username, password: passwordHash});
res.json(users);
} catch (e) {
res.status(500).send(e.toString());
}
});
Here bcrypt returns a Promise so we will be adding async. To get salt string we will be using genSalt and storing it in the salt variable. Since genSalt method returns Promise so we will await on that method.
Now time to generate a hash password - to generate a hash password we will be using bcrypt.hash method. This method takes first argument as a plain text and the second argument as a salt string and it returns a hash value with salt string. Here I have used await since it returns Promise but if you don't want to use await then you have to register a callback to get the return value.
In the next line, I have pushed user details to our users array. For real applications, you will be storing all the details in DB.

Create a post route to login user
app.post('/user/login', async (req, res) => {
try {
const user = users.find(user => user.username === req.body.username);
console.log(user);
if (!user) {
res.status(400).send('User Not Found!');
}
if (await bcrypt.compare(req.body.password, user.password)) {
res.send('LoggedIn');
} else {
res.send('Not Valid User!');
}
} catch (e) {
console.log(e.toString());
}
})
Here we have first fetched the user from our users array based on username(here we assume the username is unique for each user). In the next line we checked whether the user is present or not if the user is not present then we send response User Not Found! else we are comparing password with bcrypt.compare method. This method takes the first argument as a plain text and the second argument as a hash password. If both are equal then it returns true else returns false.
Please note I have used await keyword before bcrypt.compare method. If you missed await keyword then this will always send you a response as LoggedIn. (I have made this mistake in the past and I was debugging for almost half an hrs :)).


Here is the full code
const express = require('express');
const bcrypt = require('bcrypt');
const app = express();
let users = [];
app.use(express.json());
app.post('/user/register', async (req, res) => {
try {
const salt = await bcrypt.genSalt();
const passwordHash = await bcrypt.hash(req.body.password, salt);
users.push({username: req.body.username, password: passwordHash});
res.json(users);
} catch (e) {
res.status(500).send(e.toString());
}
});
app.post('/user/login', async (req, res) => {
try {
const user = users.find(user => user.username = req.body.username);
console.log(user);
if (!user) {
res.status(400).send('User Not Found!');
}
if (await bcrypt.compare(req.body.password, user.password)) {
res.send('LoggedIn');
} else {
res.send('Not Valid User!');
}
} catch (e) {
console.log(e.toString());
}
})
app.listen(3000);
Thanks for reading!