How To Hash Passwords In Node.js Applications Using Bcrypt

Any production-worthy codebase must be secure and safe for its users. Imagine a financial application where the users' passwords are plainly available in the database.

Such a product is way more susceptible to malicious attacks. Not long ago, threat actors stole over 8 million passwords of DailyQuiz users because they were in plaintext.

In this short tutorial, we will go over how to hash passwords from the backend for higher security. We will leverage Bcrypt, a cryptographic password hashing library to do this.

Whether you are a more advanced Node.js developer or a junior, this tutorial will be simple enough for you to follow and replicate the same thing.

A Brief Note on Bcrypt

There is a popular belief that cryptography is mainly applicable in decentralized finance and popular tokens such as Bitcoin and Ethereum.

While this is right, it is not holistically true. Cryptography has been a bedrock of privacy in engineering, including the zero-knowledge space.

That cleared, Bcrypt is a cryptographic library that complicates passwords. For instance, if a password is plainly “PharaohOfTech,” it will be encrypted as “$2a$12$kB5WhlAaQfzQYUoNPBjgXe6QlbCF/Ciofp1U3/d4TaQa9drfMXPei.”

How does it work?

Bcrypt takes in a human-readable password and transforms it into a 72-byte alphanumeric characters. Within a single encrypted password is a hash algorithm identifier, rounds identifier, salt, and computed by hash; in that order.

With the way it encrypts passwords, it protects against brute-force searching and rainbow table attacks. In fact, big companies like Linux adopted it for some of their products.

At the moment, Bcrypt does not have detailed documentation. Anyone who wants to know more about it should read and probably contribute to it on the NPM website.

Requirements

Project Initialization

Run this command to initialize your project:

npm init -y

It should initialize a JSON package, which you can modify if you want to.

Install Express and Bcrypt

Express is a Node.js framework, which we will be using. As mentioned earlier, Bcrypt is a password privacy library. Enter this command to have them both in your script:

npm i express bcrypt

Rest Client

This is a VS Code extension we will use to test our routes. Download on the VS Code extension marketplace.

Step-by-Step Guide: Building a Secure Node.js Application with Bcrypt

Create a file named server.js in which you will write the code in the subsequent subheading.

Step 1: Setting Up Express and Bcrypt Libraries

// import express with 'require'

const express = require('express')

const app = express()

// create a variable we will assign to bcrypt

const bcrypt = require('bcrypt')

Express is a popular JavaScript framework we will use in this application. Therefore, we had to import it into our application using require. Having set-up Express, the second line helps us create an Express application.

You might wonder how. Check Node Modules, search for Express, click on Lib, then check out express.js. Notice there is a createApplication function there with an app variable.

Thus, the const app = express() done above implements the inner function explained above.

Moving on, we set up the Bcrypt library in the third line.

Step 2: Creation of User Array and JSON Settings

// this allows the application to accept and interact in the JSON format

app.use(express.json())

// users variable to store the users

// use a database for prod

const users = [{ name: 'Name'}]

// route to respond with users

app.get('/users', (req, res) => {

    res.json(users)

})

JSON is a human-readable method of storing, displaying, and communicating data across several applications. We want the usernames and passwords of the users to be rendered in JSON eventually.

Therefore, the first line is a declaration that Express should process and display data in JSON format.

Data of who? The users. This prompted us to create an array of users. A quick digression here: this approach is not a smart engineering decision for production applications.

Integrate your data into a database software like Supabase. We only used this approach because this is simply an extremely basic application.

In relation to the above, the GET function stores data from the users array created earlier.

Step 3: Creation of User Routes

// creation of user routes

app.post('/users', async(req, res) => {

    try {

        // salt generation

        const salt = await bcrypt.genSalt()

        //arg 10 is the number of rounds

        const hashedPassword = await bcrypt.hash(req.body.password, 10)

        const user =  { name: req.body.name, password: hashedPassword }

    // pushes into the user array

    users.push(user)

    // sends a response back to the user

    res.status(201).send()

    } catch {

        res.status(500).send()

    }

})

This is a POST request with a predefined /users URL indicating user's path. We made the request asynchronous within the parameters for concurrent request and response interaction.

The main idea of the rest of this code block is that the user should be able to create their usernames and hashed passwords.

This was implemented with a try/catch. The first line in the try is for salt generation. Recall that salt, in the instance case, is a cryptographic add-on that makes hashed passwords harder to crack.

Then we spelt out that the users must supply their name and passwords, which will be turned to a hashed password at the backend for security reasons.

Once this is done, the user is pushed into the user array created successfully at the state.

Peradventure, the activities do not flow as defined above, there is a catch statement to return an error 500, which is an HTTP status code that shows the request cannot be generated.

Step 4: Creating the Login Route

// login route for the users

app.post('/users/login', async (req, res) => {

    // "==" for comparison, and "=" for assignment

    const user = users.find ((user) => user.name == req.body.name)

    // checks if there is not user / user == undefined or user == null

    if (!user || user == null || user == undefined) {

        return res.status(400).send("not registered")

    }

    // checks password accuracy

    const isPassword = await bcrypt.compare(req.body.password, user.password)

    if (isPassword) {

        res.send("Successfully logged-in!")

    } else {

        res.send("Unsuccessful entry")



    }

})

// listens to the connection on the port

app.listen(3000)

At this stage, the users can create their usernames and passwords but cannot log in yet.

This POST request predefines the users/login route. For authentication, we compared every user.name with the req.body.name.

Within the if statement, ‘||’ is a the logical Or operator which checks the truism of operands and returns the boolean true or false as the case may be.

The statement checks whether a user is null or undefined; if true, it returns a bad request with error 400.

Having taken care of the users, it is time to fraud-proof the password.

We created a isPassword variable to compare if the provided password actually matches the password of the purported user within the database. If this is true, the user should be given access into their account. Otherwise, access should be denied.

The last line is a statement that makes the server listens to the connection on port 3000.

At the end of the day, your server.js should be like this:

// import express with 'require'
const express = require('express')
const app = express()
// create a variable we will assign to bcrypt
const bcrypt = require('bcrypt')


// this allows the application to accept and interact in the JSON format
app.use(express.json())
// users variable to store the users
// use a database for prod
const users = [{ name: 'Name'}]
// route to respond with users
app.get('/users', (req, res) => {
    res.json(users)
})

// creation of user routes
app.post('/users', async(req, res) => {
    try {
        // salt generation
        const salt = await bcrypt.genSalt()
        //arg 10 is the number of rounds
        const hashedPassword = await bcrypt.hash(req.body.password, 10)

        const user =  { name: req.body.name, password: hashedPassword }
    // pushes into the user array
    users.push(user)
    // sends a response back to the user
    res.status(201).send()
    } catch {
        res.status(500).send()
    }
})

// login route for the users
app.post('/users/login', async (req, res) => {
    // "==" for comparison, and "=" for assignment
    const user = users.find ((user) => user.name == req.body.name)

    // checks if there is not user / user == undefined or user == null

    if (!user || user == null || user == undefined) {
        return res.status(400).send("not registered")
    }

    // checks password accuracy
    const isPassword = await bcrypt.compare(req.body.password, user.password)

    if (isPassword) {
        res.send("SucceWssfully logged-in!")
    } else {
        res.send("Unsuccessful entry")

    }

})

// listens to the connection on the port
app.listen(3000)

Testing the API with Rest Client

Run this command on your command line to run the server:

node server.js

Then create a file named request.rest where you will create a port for each route thus:

GET http://127.0.0.1:3000/users

###

POST http://127.0.0.1:3000/users
Content-Type: application/json

{
    "name": "John Fawole",
    "password": "goDaddy"
}

###

POST http://127.0.0.1:3000/users/login
Content-Type: application/json

{
    "name": "John Fawole",
    "password": "goDaddy"
}

Note: You can use other usernames and passwords for testing your API.

Once you have this in your VS Code, you will see a “Send Request” link on top of each of this requests.

  • Click on second Send Request to create a user
HTTP/1.1 201 Created
X-Powered-By: Express
Date: Thu, 23 Nov 2023 04:38:36 GMT
Connection: close
Content-Length: 0
  • Click on the first one to get a hashed password for the username
[

  {

    "name": "Name"
  },
  {

    "name": "John Fawole",
    "password": "$2b$10$WxU8Q81y5pS37d8Z5wFufO9Nb8J84BGT97yhbDa7mwEVUNk8PzTX6"
  }
]
  • Click on the third one to test if you can login with the right details
Successfully logged-in!

Note: If you change the password to anything else, you will get this response:

Unsuccessful entry

Wrapping Up

In this tutorial, I have taught you how to authenticate users in your Node.js application using Bcrypt. You should also try out Platformatic instead of Express on the framework side; for the encryption library, try building with Passport.

The goal of being able to build the same application using different libraries and frameworks is to build engineering versatility.

Here is my full code if it helps. At this point, I must acknowledge the input of John Tochukwu who helped out when I was debugging an issue in this code. You can go ahead to follow him on Twitter.

Happy hacking!