Introduction
Passwords are the first line of defence for almost every online application. When you’re building a social media platform, an e-commerce store, or an internal business portal user password protection should be your top priority.
- Introduction
- What Is Password Hashing?
- What Is bcrypt?
- How Does bcrypt Work?
- Step 1: User types password
- Step 2: bcrypt Generates a Salt
- Step 3: Combinding Password & Salt
- Step 4. Hashing It
- Step 5: User Sign In
- Installing bcrypt in Node.js
- Your First Password Hash
- Building a Secure Registration and Login System Using bcrypt in Node.js
- Create a New User Account
- Understanding bcrypt.compare()
- Environment Variables
- Real-World Illustration
- Best Tips
- Common Errors to Avoid
But many beginner developers make one big mistake: they store passwords in plain text. If a database is hacked , all the passwords are instantly compromised . This can lead to account takeovers, identity theft and serious security issues.
But happily there is a much safer way. Instead of storing the passwords in the database directly, you should hash the passwords before storing them. One of the most trusted libraries for this is bcrypt.
In this guide, you’ll learn how bcrypt works, why it’s more secure than older hashing algorithms, and how to use it in a Node.js application. You will also get a basis for a secure authentication system that follows modern security best practices.
Why You Should Never Store Plain Text Passwords
Imagine you are building a user registration system. A user signs up with the following credentials:
john@example.com
Password
MyPassword123
A beginner might store these values like this:
| Password | |
|---|---|
| john@example.com | MyPassword123 |
At first sight this looks harmless. But it is a major security hazard.
If an attacker gets into your database, they immediately know every user’s password. Many people also use the same password on multiple sites. That means a single breach could leave email accounts, banking apps and social media profiles exposed.
So, storing plain text passwords is one of the biggest security mistakes a developer can make.
What Is Password Hashing?
Password hashing is the method of converting a password into a fixed length string of characters by using a mathematical algorithm.
Hashing is a one-way process, and it is not an encryption. The password is hashed and cannot be ” dehashed “.
For instance:
Current password
MyPassword123
Hashed Password
$2b$10$Mdbj6x7vI5S8rP2r4vY4eOlv8bY9nFv0L3xS6K9zM1JqA7BcD8EfG
The hashed value looks random but it always represents the original password.
When the user logs in again, the password is bcrypt hashed and compared with the stored hash. If they match, then authentication passes.

What Is bcrypt?
bcrypt is a password hashing library designed to store passwords securely.
Unlike older hashing algorithms that use a static salt , bcrypt automatically generates a unique salt for each password . It also allows developers to specify how computationally intensive the hashing operation should be.
When computer hardware gets faster you can increase the cost factor used by bcrypt to make it harder to crack passwords.
Currently, bcrypt is used extensively in:
- Node.js apps
- Express.js API’s
- Laravel-Projekte
- Ruby on Rails apps
- Django apps
- Enterprise authentication technologies
Bcrypt is still one of the recommended password hashing algorithms due to its proven reliability.
How Does bcrypt Work?
So when a user creates a password , bcrypt does a few things .
Step 1: User types password
For instance: MyPassword123
Step 2: bcrypt Generates a Salt
A salt is a random unique value per password.
Now, since each password is mixed with a different salt, even if two users have the same password, their hashes will still be completely different.
Step 3: Combinding Password & Salt
The password is then combined with the generated salt and hashed using bcrypt.
Step 4. Hashing It
The password is not stored in the database , but the hash .
Step 5: User Sign In
Later, when the user logs in, bcrypt hashes the password entered using the stored salt. It then compares the new hash with the current hash.
If both hashes are equal then login is successful .
Thus, the original password is never needed to be stored anywhere.
Understanding Salt
A salt is one of bcrypt’s most important security features.
Without a salt, two users who choose the same password would produce identical hashes.
For example:
| User | Password | Hash |
| Alice | password123 | Same Hash |
| Bob | password123 | Same Hash |
This makes password databases easier to attack.
With bcrypt, every password receives a different random salt.
| User | Password | Hash |
| Alice | password123 | Different Hash |
| Bob | password123 | Different Hash |
Therefore, attackers cannot easily determine which users share the same password.
bcrypt vs MD5 vs SHA-256
Many beginners wonder why they should use bcrypt instead of MD5 or SHA-256.
Here’s a comparison:
| Feature | bcrypt | SHA-256 | MD5 |
| Built for Passwords | ✅ Yes | ❌ No | ❌ No |
| Automatic Salt | ✅ Yes | ❌ No | ❌ No |
| Adjustable Cost Factor | ✅ Yes | ❌ No | ❌ No |
| Resistant to Brute Force | ✅ High | ⚠️ Moderate | ❌ Poor |
| Recommended Today | ✅ Yes | ⚠️ Only with additional safeguards | ❌ No |
Though the SHA-256 algorithm is safe for many cryptographic operations, it is meant to be fast. However, password hashing has to be done deliberately slowly in order to make brute force attacks costly.
This makes bcrypt more suitable for password storage.
Installing bcrypt in Node.js
Before you write any code, create a new Node.js project.
Begin the project:
npm init -y
Then install bcrypt:
npm install bcrypt
After the installation, import the package into your project:
const bcrypt = require(“bcrypt”);
You are now ready to begin hashing passwords properly.
Your First Password Hash
Let’s hash a password using bcrypt.
const bcrypt = require("bcrypt");
async function hashPassword() {
const password = "MyPassword123";
const hashedPassword = await bcrypt.hash(password, 10);
console.log(hashedPassword);
}
hashPassword();
In this case, the number 10 is the salt rounds number, which is often referred to as the cost factor. The larger the number, the greater the security; however, more time will be spent on the computation process.
For the majority of web applications, the number of 10-12 salt rounds is considered sufficient from both the security and performance perspectives.
As soon as you execute the script, you will notice that a different hash will be generated because bcrypt generates a different salt each time.
It is absolutely natural because of the following reason.
Building a Secure Registration and Login System Using bcrypt in Node.js
Now that you understand how bcrypt works, let’s implement it in a real-world application. In this section, you’ll create a very simple authentication system with Express.js, MongoDB and bcrypt.
After this tutorial you will be able to:
- Securely register new users.
- Hash passwords before saving them.
- Check passwords at login.
- Create authentication tokens.
- Adhere to best practices in the industry.
Project Setup
First, create a new project folder.
mkdir bcrypt-auth-demo
cd bcrypt-auth-demo
Next, initialize a Node.js project.
npm init -y
Now install the required packages.
npm install express mongoose bcrypt jsonwebtoken dotenv
Install Nodemon for development.
npm install --save-dev nodemon
Your project structure should look like this:
bcrypt-auth-demo/
│
├── models/
│ User.js
│
├── routes/
│ auth.js
│
├── .env
├── server.js
└── package.json
Keeping your project organized makes it easier to maintain as it grows.
Creating the Express Server
Create a file named server.js.
const express = require("express");
const mongoose = require("mongoose");
require("dotenv").config();
const app = express();
app.use(express.json());
mongoose.connect(process.env.MONGO_URI)
.then(() => console.log("MongoDB Connected"))
.catch(err => console.log(err));
app.listen(3000, () => {
console.log("Server running on port 3000");
});
This code creates an Express server and connects it to MongoDB.
Creating the User Model
Inside the models folder, create User.js.
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
name:String,
email:{
type:String,
unique:true
},
password:String
});
module.exports = mongoose.model("User",userSchema);
This schema stores:
- Name
- Hashed password
Notice that we never plan to store the original password.
Creating the Registration API
Now create routes/auth.js.
Import the required modules.
const express = require("express");
const bcrypt = require("bcrypt");
const User = require("../models/User");
const router = express.Router();
Create the registration endpoint.
router.post("/register", async(req,res)=>{
const {name,email,password}=req.body;
const existingUser=await User.findOne({email});
if(existingUser){
return res.status(400).json({
message:"User already exists"
});
}
const hashedPassword=await bcrypt.hash(password,10);
const user=new User({
name,
email,
password:hashedPassword
});
await user.save();
res.status(201).json({
message:"Registration Successful"
});
});
Let’s get a grip on what’s going on.
The API will then verify whether the email already exists.
If it does, registration ceases immediately.
Otherwise, bcrypt hashes the password
Finally only the hashed password is stored in the database.
This method ensures sensitive user credentials are kept safe.
Create a New User Account
Now suppose a user enters the following data.
{ “name”: “John”, “email”: “john@gmail.com”, “password”: “Password123” }
Instead of saving:
Password123
MongoDB has something like this:
$2b$10$0WJ7XWkDiL…..
Even if someone gets into your database they can’t read the original password.
Creating the Login API
Registration is only half of the authentication process.
Users also need to log in securely.
Create the login route.
router.post("/login",async(req,res)=>{
const {email,password}=req.body;
const user=await User.findOne({email});
if(!user){
return res.status(404).json({
message:"User not found"
});
}
const isMatch=await bcrypt.compare(
password,
user.password
);
if(!isMatch){
return res.status(401).json({
message:"Invalid Password"
});
}
res.json({
message:"Login Successful"
});
});
Unlike registration, login does not hash the password manually.
It then compares the entered password with the stored hash, by the bcrypt algorithm.
If both values match, the authentication succeeds.
Understanding bcrypt.compare()
This causes many beginners to ask why we cannot compare strings.
Like for example:
password===user.password
This will never work because:
Password123
is totally different from:
$2b$10$4jKf9…
More bcrypt under the hood:
- Salt is extracted.
- Rehashes the password entered again.
- Compares both hash values securely.
This means developers never have to decrypt passwords.
Generating JWT Tokens
Most modern APIs use JSON Web Tokens (JWT).
Install the package.
npm install jsonwebtoken
Import it.
const jwt=require("jsonwebtoken");
Generate a token after successful login.
const token=jwt.sign(
{
id:user._id
},
process.env.JWT_SECRET,
{
expiresIn:"1d"
}
);
res.json({
message:"Login Successful",
token
});
Now the client receives a secure token instead of maintaining a server-side session.
This makes authentication scalable and suitable for REST APIs.
Environment Variables
Don’t ever put secrets directly into your code.
Instead, create a .env file.
PORT=3000
MONGO_URI=mongodb://localhost/auth
JWT_SECRET=myverystrongsecretkey
Then load it with:
require(“dotenv”).config();
Using environment variables is more secure and keeps sensitive information out of source control.
Complete Authentication Flow
Here’s how the entire process works:
User Registers
↓
Password Entered
↓
bcrypt Generates Salt
↓
Password Hashed
↓
Hash Stored in MongoDB
↓
User Logs In
↓
bcrypt Compares Password
↓
JWT Token Generated
↓
Access Granted
This workflow is used in many real-world applications.
Real-World Illustration
Suppose you’re building a shopping website.
Customer creates an account.
Rather than saving:
SummerSale2026
The database includes:
$2b$10$khH93j8ksl…
Then the customer logs in.
bcrypt never exposes or decrypts the password, it just verifies it.
So even if the database is leaked, attackers cannot immediately access customer accounts.
Best Tips
Follow these recommendations for using bcrypt.
- Use 10-12 salt rounds for most applications.
- Always validate user input.
- Store secrets in environment variables.
- “Use HTTPS in production.”
- Limit the number of login attempts.
- Lock out account after too many wrong logins.
- Utilize JWT expiration times.
- Update dependencies.
If you do these things you will make your authentication system much more secure.
Common Errors to Avoid
Avoid these common errors:
❌ Save plain text passwords
❌ MD5 stored passwords
❌ Hard coded secret keys
❌ Bypass email validation
❌ Not paying attention to failed login attempts
❌ Sending detailed authentication errors
Instead, send generic messages like:
Wrong email or password.
This prevents attackers from learning whether an email address exists in your system.