Learn how to securely store passwords, hash passwords using bcrypt, validate passwords, and protect user accounts in Node.js applications.
Password security means protecting users' passwords from unauthorized access. If a database is leaked, securely hashed passwords are much harder to recover than plain-text passwords.
{
"username": "rahul",
"password": "rahul123"
}
{
"username": "rahul",
"password": "$2b$12$exampleHashValue..."
}
A real bcrypt hash includes algorithm and cost information. The example above is illustrative, not a valid hash.
Password hashing converts a password into a derived value using a one-way password-hashing algorithm. The original password should not be recoverable from the stored hash.
Password
|
v
Password Hashing Algorithm
|
v
Password Hash Stored in Database
In this lesson, we will use bcryptjs to hash and verify
passwords. Argon2id is also a strong option for new applications.
npm init -y
npm install bcryptjs
Create a file named hash_password.js.
const bcrypt = require('bcryptjs');
async function hashPassword() {
const password = 'Rahul@123';
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(
password,
saltRounds
);
console.log('Original Password:', password);
console.log('Hashed Password:', hashedPassword);
}
hashPassword().catch(console.error);
Original Password: Rahul@123
Hashed Password: $2b$12$...
The actual hash will be longer and will differ between executions because bcrypt generates a random salt.
When a user logs in, compare the submitted password with the stored
hash using bcrypt.compare(). Never compare the submitted
password directly with the hash as a string.
const bcrypt = require('bcryptjs');
async function verifyPassword() {
const password = 'Rahul@123';
const storedHash = await bcrypt.hash(password, 12);
const enteredPassword = 'Rahul@123';
const isMatch = await bcrypt.compare(
enteredPassword,
storedHash
);
if (isMatch) {
console.log('Password is correct');
} else {
console.log('Invalid password');
}
}
verifyPassword().catch(console.error);
Password is correct
The following Express example demonstrates registration and login using bcrypt. It stores users in memory for learning purposes only. Users disappear when the server restarts.
npm install express bcryptjs
const express = require('express');
const bcrypt = require('bcryptjs');
const app = express();
app.use(express.json());
// In-memory storage for learning only
const users = [];
app.post('/register', async (req, res) => {
try {
const { username, password } = req.body;
if (
typeof username !== 'string' ||
typeof password !== 'string' ||
!username.trim() ||
password.length < 12
) {
return res.status(400).json({
message: 'Enter a username and a password of at least 12 characters.'
});
}
const normalizedUsername = username.trim().toLowerCase();
const existingUser = users.find(
user => user.username === normalizedUsername
);
if (existingUser) {
return res.status(409).json({
message: 'Unable to create this account.'
});
}
const hashedPassword = await bcrypt.hash(password, 12);
users.push({
username: normalizedUsername,
password: hashedPassword
});
res.status(201).json({
message: 'Registration successful'
});
} catch (error) {
console.error('Registration error');
res.status(500).json({
message: 'Something went wrong'
});
}
});
app.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
if (
typeof username !== 'string' ||
typeof password !== 'string'
) {
return res.status(400).json({
message: 'Invalid username or password'
});
}
const normalizedUsername = username.trim().toLowerCase();
const user = users.find(
user => user.username === normalizedUsername
);
if (!user) {
return res.status(401).json({
message: 'Invalid username or password'
});
}
const isMatch = await bcrypt.compare(
password,
user.password
);
if (!isMatch) {
return res.status(401).json({
message: 'Invalid username or password'
});
}
res.json({
message: 'Login successful'
});
} catch (error) {
console.error('Login error');
res.status(500).json({
message: 'Something went wrong'
});
}
});
app.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
node password_app.js
Send a POST request to http://localhost:3000/register
with a JSON body:
{
"username": "rahul",
"password": "MyStrongPassword@123"
}
Successful response:
{
"message": "Registration successful"
}
Send a POST request to http://localhost:3000/login
with the same username and password.
{
"username": "rahul",
"password": "MyStrongPassword@123"
}
Successful response:
{
"message": "Login successful"
}
| Rule | Explanation |
|---|---|
| Hash passwords | Use Argon2id or bcrypt with an appropriate work factor. |
| Never store plain text | Save only the password hash. |
| Use unique salts | Modern password-hashing libraries generate salts automatically. |
| Use HTTPS | Protect passwords while they travel between the client and server. |
| Validate inputs | Check types, lengths, and reasonable input limits. |
| Limit login attempts | Use rate limiting and monitoring to reduce brute-force attacks. |
| Use generic login errors | Avoid revealing whether a username or email exists. |
| Keep secrets private | Do not commit database credentials or signing secrets to source control. |
| Support secure recovery | Use short-lived, single-use password reset tokens. |
MD5 and ordinary SHA-256 hashing are designed to be fast. That makes them unsuitable for directly storing passwords because attackers can test many guesses quickly.
// Do not use this for password storage:
const crypto = require('crypto');
const hash = crypto
.createHash('sha256')
.update('Rahul@123')
.digest('hex');
Complete the following tasks:
/register route./login route.bcrypt.compare().bcrypt.hash() creates a password hash.bcrypt.compare() verifies a submitted password.Choose the correct answer for each question.
1. Which function hashes a password using bcryptjs?
2. Which function checks a password against a stored bcrypt hash?
3. Which is the safest password-storage approach?