Photo by Rubaitul Azad on Unsplash
Securely Saving Passwords in MongoDB Through Node.js
Securely Store Passwords in MongoDB Through Node.js with bcrypt Hashing for Maximum Security
It is important to protect sensitive data stored in web application databases from malicious actors. To do this effectively, it is essential that we use hashing instead of storing the data in plaintext. Hashing is a complex process which converts plaintext into a non-readable string of characters, which cannot be reversed or decrypted to the original text. By using hashing, it makes it much more difficult for malicious actors to gain access to the sensitive data and use it for malicious purposes, helping to ensure the security of that data.
This article covers the use of bcrypt to securely store user passwords in MongoDB through Node.js. It explains the project structure, setting up express web server, configuring MongoDB, creating an auth controller, and testing with postman. In conclusion, bcrypt is a secure algorithm for password hashing and is recommended for safe storage of sensitive data.
In this article, we'll use the npm library bcrypt to store user passwords in MongoDB. To demonstrate this, let's take an example of the sign-in process for a user. To keep things simple, there are just two fields: username
and password
.
In the end, we will construct the project structure in this way.
First, we will install the npm libraries.
npm install express mongodb bcryptjs body-parser cors --save
You can use mongoose
instead of mongodb
.
Creating an Express web server
Let's create a new file called server.js
in the root folder, which will be our main file.
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
const corsOptions = {
origin: 'http://localhost:8081'
}
app.use(cors(corsOptions));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(function (req, res, next) {
res.header(
"Access-Control-Allow-Headers",
"x-access-token, Origin, Content-Type, Accept"
);
next();
});
app.get('/', (req, res) => {
res.json({ message: 'Welcome to Mongodb auth' });
});
require('./app/routes/auth.routes')(app);
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Server running at ${PORT}`);
});
We are using body-parser
to create the req.body
object by parsing the request.
Setting up MongoDB
We will create a file called db.config.js
within the config folder of the app, which will store all database-related configurations.
module.exports = {
HOST:"localhost",
PORT: 27017,
DB: "bcryptdemo",
USER: "users"
}
Let's now create a file called mongodb.js
in the root folder for connecting.
const dbConfig = require('./config/db.config');
const MongoClient = require('mongodb').MongoClient;
let conn;
exports.getConnection = async () => {
if (conn) {
return conn;
}
try {
conn = await MongoClient.connect(`mongodb://${dbConfig.HOST}:${dbConfig.PORT}/`, {
useNewUrlParser: true,
useUnifiedTopology: true
});
if (conn) console.log('Mongodb connected successfully!');
return conn;
}
catch (err) {
console.log('Error in mongodb connection', err);
process.exit();
}
}
Creating an Auth Controller
We have a signup and signin function to demonstrate hashing.
Signup
const bcrypt = require('bcryptjs');
const dbConfig = require('../config/db.config');
const conn = require('../mongodb');
exports.signup = async (req, res) => {
const client = await conn.getConnection();
const db = client.db(`${dbConfig.DB}`);
bcrypt.hash(req.body.password, 12)
.then(function (hashedPass) {
const user = {
username: req.body.username,
password: hashedPass
}
db.collection(`${dbConfig.USER}`).insertOne(user);
res.send({ message: 'Registered user successfully!' });
}).catch((err) => {
return res.status(500).send({ message: err });
});
}
The bcrypt hash function takes two parameters - the password to be hashed, and the salt or saltrounds. In this case, it's set to 12, which means the bcrypt module will go through 2^12 iterations in order to hash the data.
The result of hash function looks like $[algorithm]$[cost]$[salt][hash]
$2a$12$A1iqBUXMMVEUAIimj9FJmeGLDwaLOz6g2i13WKVHEg3rteKLEYvLy
| | | |
| | | hash-value = GLDwaLOz6g2i13WKVHEg3rteKLEYvLy
| | |
| | salt = A1iqBUXMMVEUAIimj9FJme (base64 encoded to 22 characters)
| |
| cost-factor => 12 = 2^12 rounds
|
hash-algorithm identifier => 2a = BCrypt
Signin
To sign in, we need to find the hashed password from the database and use the compare function to compare the plaintext password with the hashed one.
exports.signin = async (req, res) => {
const client = await conn.getConnection();
const db = client.db(`${dbConfig.DB}`);
db.collection(`${dbConfig.USER}`).find({ username: req.body.username }).toArray((err, user) => {
if (err) return res.status(500).send({ message: err });
if (!user[0]) return res.status(404).send({ message: 'User not found' });
bcrypt.compare(req.body.password, user[0].password).then((isPassValid)=>{
if (!isPassValid) return res.status(401).send({ message: 'Invalid Password!' });
res.send({
id: user[0]._id,
username: user[0].username,
message: 'Signed in successfully!'
});
}).catch((err) => {
return res.status(500).send({ message: err });
});
});
}
compare function returns a boolean.
We can add middleware to prevent duplicate entries when signing up.
app.post("/auth/signup", [verifySignUp.checkDuplicateUsername], AuthController.signup);
In verifySignUp.js
, let's add the below code snippet:
checkDuplicateUsername = async (req, res, next) => {
const client = await conn.getConnection();
const db = client.db(`${dbConfig.DB}`);
db.collection(`${dbConfig.USER}`).find({ username: req.body.username }).toArray((err, user) => {
if (err) return res.status(500).send({ message: err });
if (user[0]) {
return res.status(400).send({ message: `Username ${user[0].username} already exists` });
}
next();
});
};
Testing
We will use Postman to test the signup and sign-in process and check MongoDB to see if the password was saved.
For some negative test cases:
Let's try adding the same user!
Entering an incorrect password.
We can view the password in its hashed form in MongoDB.
In conclusion, bcrypt is a secure algorithm for password hashing and is recommended for safe storage of sensitive data. It is more secure than other methods of password hashing due to its use of a cost factor, which makes it more difficult for attackers to brute force the password. Additionally, it is an easy to use library that is available in Node.js and can be easily integrated into web applications.