Password hashing in Node.js with bcrypt

Boosting your Node.js app's safety: a guide to keeping user passwords secure with bcrypt. Learn how to safeguard user accounts and data in your applications for better peace of mind.

In the digital age, security is of paramount importance, especially when it comes to user data and credentials. As developers, we must ensure that sensitive information, such as passwords, is stored securely to prevent unauthorized access and potential breaches. One of the key techniques employed to enhance security is password hashing, and in the realm of Node.js development, the bcrypt library stands out as a robust solution for this purpose.

In this comprehensive article, we will delve into the world of password hashing and explore how to implement it effectively using the bcrypt library in a Node.js application. We'll cover the fundamental concepts of password security, the drawbacks of storing plain passwords, and how bcrypt addresses these challenges. By the end of this article, you'll have a solid understanding of the importance of password hashing and be equipped with practical knowledge to integrate bcrypt into your Node.js projects for safeguarding user credentials.

Why password security matters

Passwords have been the go-to method for proving your identity when using websites and apps for a while. However, there's a big problem with them; if they're not protected well, like being kept in plain text or with weak encryption, an attacker who gets into the system can easily gain access to your account and private information. This is a danger for both the average user and businesses.

To mitigate this risk, password security protocols aim to change plain text passwords into an unintelligible format that cannot be easily reversed. This process, known as password hashing, involves applying cryptographic algorithms to the original password and generating a hash value that appears random and unrelated to the original input. Even if an attacker gains access to the hashed passwords, it should be quite difficult for them to convert it to the original password.

The pitfalls of storing plain passwords

Storing passwords in plain text within databases might seem like an easy way to keep track of them, but it's a massive security problem. When a database gets hacked, attackers can instantly see everyone's login details. This is especially bad news for people who use the same passwords for lots of different websites because all their accounts are now vulnerable. However, even if you consistently use unique passwords, there's still a risk because of something called "rainbow table attacks". This is when attackers use pre-made lists of hash values to quickly match them up with stolen hashes.

To deal with these problems, we use something called "password hashing". It's like supercharging the security of an app. One popular tool for this is called "bcrypt", which is proven to be highly effective at protecting passwords. It makes it difficult for attackers to guess passwords by trying lots of combinations and thwarts rainbow table attacks.

Introducing bcrypt

Bcrypt is a widely used library for securely hashing passwords. It relies on the Blowfish cipher and a technique called "salting" to hash passwords. Salting involves adding a random value (called the "salt") to the original password before hashing it. This ensures that even if two users happen to have the same password, their hashed values will be different because of the unique salt added to each one.

By using a cryptographically secure hash function, bcrypt significantly slows down the hashing process, making it computationally expensive. This is a good thing because it makes it much harder for attackers to use brute-force methods to guess passwords.

In the upcoming sections of this article, we'll take a deep dive into how bcrypt works. We'll look at how it can be used within Node.js applications and break down the steps needed to hash and check passwords securely. We'll provide practical examples to help you understand how to use bcrypt effectively to enhance the security of your application.

Understanding bcrypt's workings

To fully appreciate the power of bcrypt, it's important to delve into the underlying mechanisms that make it an excellent choice for password hashing in Node.js applications. Let's break down the key components and processes involved in bcrypt's operation:

  • Salt generation

The first step in bcrypt's password hashing process is the generation of a unique salt for each password. A salt is a random value that is combined with the password before hashing. This ensures that even if two users have the same password, their resulting hash values will differ due to the unique salt applied.

  • Key stretching

bcrypt employs a process called key stretching to slow down the hashing process. Key stretching involves repeatedly applying a cryptographic hash function multiple times. This deliberate slowdown is intentional, as it thwarts brute-force attacks by forcing attackers to spend significantly more time and computational resources trying various password combinations. The number of times the hash function is iterated is controlled by a parameter known as the "work factor" or "cost factor". A higher work factor increases the time and resources required to compute the hash, thereby enhancing the security of the hashed passwords.

  • Password hashing

Once the salt is generated and the work factor is determined, bcrypt combines these values with the user's password and passes them through the Blowfish cipher. The result is a cryptographic hash that represents the password and the associated salt. The resulting hash is a fixed-length value, which means that regardless of the length of the original password, the hash length remains constant. This is a valuable property for securely storing and comparing password hashes.

  • Verification process

When a user attempts to log in, the application retrieves the stored hash from the database and applies the same salt and work factor during the verification process. The user's provided password is hashed using these parameters, and the resulting hash is compared with the stored hash. If they match, the provided password is correct; otherwise, authentication fails. By incorporating salts, key stretching, and a cryptographically secure hashing algorithm, bcrypt ensures that password hashes are resistant to various types of attacks, providing a robust defense against unauthorized access.

Integrating bcrypt into Node.js applications

Now that we have a solid grasp of bcrypt's inner workings, let's explore how to integrate bcrypt into your Node.js applications for secure password hashing. The process can be broken down into a few simple steps.

First, install the bcrypt library using npm or yarn. Open your terminal and run the following command:

npm install bcrypt

This will install the bcrypt package and make it available for use in your Node.js project.

Hashing a password:

To hash a user's password during registration, follow these steps:

const bcrypt = require('bcrypt');
const saltRounds = 10; // You can adjust this value as needed

const plainPassword = 'user_password';

bcrypt.genSalt(saltRounds, function(err, salt) {
    bcrypt.hash(plainPassword, salt, function(err, hash) {
        if (err) throw err;
        // Store the 'hash' in your database
    });
});

Adjust the saltRounds value according to the desired level of security. Higher values will result in slower hash generation.

Verifying passwords: When verifying a user's login attempt, use bcrypt to compare the stored hash with the provided password:

const bcrypt = require('bcrypt');
const storedHash = 'stored_hash_from_database';
const userProvidedPassword = 'user_input_password';

bcrypt.compare(userProvidedPassword, storedHash, function(err, result) {
    if (err) throw err;
    if (result === true) {
        // Passwords match, grant access
    } else {
        // Passwords do not match, deny access
    }
});

By following these steps, you can seamlessly integrate bcrypt into your Node.js application, ensuring that user passwords are securely hashed and verified.

Practical implementation of bcrypt in a Node.js application

Now that we have a solid theoretical understanding of bcrypt, let's dive into a practical example of how to implement bcrypt for secure password hashing in a real-world Node.js application. For this example, we'll consider a simple user authentication system using Node.js, Express, and MongoDB. We'll walk through the process of registering users with hashed passwords, verifying passwords during login, and applying best practices for enhanced security.

Set up the project

Create a new directory for your project and navigate to it in your terminal. Run npm init to initialize a new Node.js project and follow the prompts to set up your package.json.

Install dependencies

Install the necessary packages for your project:

npm install express mongoose bcrypt

Create the Express application

Create a new file, app.js, and set up your Express application:

const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

const app = express();

// Set up middleware and routes here

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server started on port ${PORT}`);
});

Set up MongoDB

Connect to your MongoDB database using Mongoose:

mongoose.connect('mongodb://localhost:27017/myapp', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useCreateIndex: true,
});

const db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
db.once('open', () => {
  console.log('Connected to MongoDB');
});

Create a user schema and model

Define a user schema and create a model using Mongoose:

const userSchema = new mongoose.Schema({
  username: { type: String, unique: true },
  password: String,
});

const User = mongoose.model('User', userSchema);

Register users with hashed passwords

Implement the registration route to hash passwords and save users:

app.post('/register', async (req, res) => {
  const { username, password } = req.body;

  try {
    const saltRounds = 10;
    const hashedPassword = await bcrypt.hash(password, saltRounds);

    const newUser = new User({ username, password: hashedPassword });
    await newUser.save();

    res.status(201).send('User registered successfully');
  } catch (error) {
    console.error(error);
    res.status(500).send('An error occurred');
  }
});

Verify passwords during login

Implement the login route to verify passwords:

app.post('/login', async (req, res) => {
  const { username, password } = req.body;

  try {
    const user = await User.findOne({ username });

    if (!user) {
      return res.status(404).send('User not found');
    }

    const passwordMatch = await bcrypt.compare(password, user.password);

    if (passwordMatch) {
      res.send('Login successful');
    } else {
      res.status(401).send('Invalid credentials');
    }
  } catch (error) {
    console.error(error);
    res.status(500).send('An error occurred');
  }
});

Best practices for using bcrypt in Node.js

While bcrypt is a powerful tool for securing passwords in Node.js applications, there are several best practices you should keep in mind to ensure it’s used effectively and responsibly. Let's delve into these practices to maximize the security of your application:

  1. Choose an appropriate salt rounds value:

    The saltRounds parameter determines the computational cost of hashing a password. A higher value increases the time it takes to hash a password, thereby making brute-force attacks more difficult. However, this also means more processing time, so strike a balance between security and performance. The most common value is around 10, but you might need to adjust it based on your application's requirements.

  2. Use a secure random number generator:

    The quality of the salt is crucial. Ensure you're using a secure random number generator to create the salt. Node.js provides the crypto module, which offers strong cryptographic primitives, including random number generation.

  3. Update bcrypt regularly:

    Like any software library, bcrypt could have vulnerabilities that are discovered over time. Make sure to keep your dependencies up to date and monitor the bcrypt project for any security updates. Regularly updating the library in your project helps you stay protected against known vulnerabilities.

  4. Use HTTPS and Other security measures:

    While bcrypt is a robust method for password hashing, it's not the only aspect of securing user data. Utilize HTTPS to encrypt data in transit and follow other security best practices, such as input validation, to prevent other attack vectors.

  5. Consider two-factor authentication:

    Two-factor authentication (2FA) adds an extra layer of security by requiring users to provide a second piece of information, such as a code sent to their smartphone, in addition to their password. Implementing 2FA can significantly enhance the security of user accounts.

  6. Implement account lockout:

    To protect against brute-force attacks, implement account lockout mechanisms that temporarily lock an account after a certain number of failed login attempts. This prevents attackers from making unlimited login attempts, rendering brute-force attacks ineffective.

  7. Routinely audit and monitor:

    Routinely audit your application's security practices and monitor your logs for any suspicious activity. This proactive approach helps identify and address potential security breaches before they escalate.

  8. Use libraries with active maintenance:

    While bcrypt is widely used and trusted, it's important to use libraries that have active maintenance and a responsive development community. This ensures that any potential issues are addressed promptly.

Strengthening security with bcrypt in Node.js

bcrypt is a crucial tool for enhancing password security in Node.js applications. We've explored its core processes, from generating salts to creating secure hashes, and explained how to practically implement it for user authentication. Remembering best practices, such as setting strong salt rounds and staying updated, ensures the effective protection of user data. By using bcrypt, we can confidently safeguard passwords, bolster application security, and maintain user confidence in today's digital environment.

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial
    Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
    Start free trial
    “Wow — Customers are blown away that I email them so quickly after an error.”
    Chris Patton, Founder of Punchpass.com
    Start free trial