How to Implement Secure User Authentication with Passport.js

How to Implement Secure User Authentication with Passport.js

User authentication is a critical component of most web applications. It ensures that only authorized users can access certain resources or perform specific actions. Implementing secure user authentication can be challenging, but with the help of Passport.js, a popular authentication middleware for Node.js, the process becomes much more manageable.

In this blog post, we’ll walk through how to implement secure user authentication using Passport.js. We’ll cover the following topics:

  1. What is Passport.js?

  2. Setting Up a Node.js Project

  3. Implementing Local Authentication

  4. Adding Password Hashing with bcrypt

  5. Using Sessions for Persistent Login

  6. Protecting Routes with Middleware

  7. Conclusion

Let’s get started!

1. What is Passport.js?

Passport.js is an authentication middleware for Node.js that provides a simple and flexible way to handle user authentication. It supports a wide range of authentication strategies, including:

  • Local authentication (username and password)

  • OAuth (e.g., Google, Facebook, GitHub)

  • OpenID

  • JWT (JSON Web Tokens)

Passport.js is highly modular, allowing developers to choose the strategies that best fit their application’s needs.

2. Setting Up a Node.js Project

Before diving into authentication, let’s set up a basic Node.js project.

Step 1: Initialize a Node.js Project

Create a new directory for your project and initialize it with npm:

mkdir passport-auth-demo
cd passport-auth-demo
npm init -y

Step 2: Install Required Dependencies

Install the necessary packages:

npm install express passport passport-local express-session bcrypt
  • express: A web framework for Node.js.

  • passport: The core Passport.js library.

  • passport-local: A strategy for authenticating with a username and password.

  • express-session: Middleware for managing user sessions.

  • bcrypt: A library for hashing passwords.

Step 3: Create the Basic Server

Create an app.js file and set up a basic Express server:

const express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');

const app = express();
const PORT = 3000;

// Middleware
app.use(express.urlencoded({ extended: true }));
app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
}));
app.use(passport.initialize());
app.use(passport.session());

// Routes
app.get('/', (req, res) => {
  res.send('Welcome to the home page!');
});

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

3. Implementing Local Authentication

Local authentication involves verifying a user’s credentials (username and password) against a database. Let’s implement this step by step.

Step 1: Create a User Model

For simplicity, we’ll use an in-memory array to store users. In a real-world application, you’d use a database like MongoDB or PostgreSQL.

const users = [];

// Helper function to find a user by username
const findUserByUsername = (username) => {
  return users.find(user => user.username === username);
};

Step 2: Configure Passport Local Strategy

Passport uses strategies to authenticate users. Here, we’ll configure the LocalStrategy:

passport.use(new LocalStrategy((username, password, done) => {
  const user = findUserByUsername(username);
  if (!user) {
    return done(null, false, { message: 'User not found' });
  }

  bcrypt.compare(password, user.password, (err, result) => {
    if (err) throw err;
    if (result) {
      return done(null, user);
    } else {
      return done(null, false, { message: 'Incorrect password' });
    }
  });
}));

Step 3: Serialize and Deserialize Users

Passport needs to serialize and deserialize users to maintain sessions:

passport.serializeUser((user, done) => {
  done(null, user.username);
});

passport.deserializeUser((username, done) => {
  const user = findUserByUsername(username);
  done(null, user);
});

4. Adding Password Hashing with bcrypt

Storing plain-text passwords is a security risk. Instead, we’ll hash passwords using bcrypt.

Step 1: Hash Passwords During Registration

Here’s how to hash a password when creating a new user:

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

  users.push({ username, password: hashedPassword });
  res.redirect('/login');
});

Step 2: Verify Hashed Passwords During Login

The LocalStrategy we configured earlier already uses bcrypt.compare to verify passwords.

5. Using Sessions for Persistent Login

Sessions allow users to remain authenticated across multiple requests. We’ve already set up express-session in our server.

Step 1: Protect Routes with Authentication Middleware

Create a middleware function to ensure only authenticated users can access certain routes:

const ensureAuthenticated = (req, res, next) => {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect('/login');
};

Step 2: Add Login and Logout Routes

Here’s how to implement login and logout functionality:

app.post('/login', passport.authenticate('local', {
  successRedirect: '/dashboard',
  failureRedirect: '/login',
  failureFlash: true,
}));

app.get('/logout', (req, res) => {
  req.logout();
  res.redirect('/');
});

6. Protecting Routes with Middleware

Use the ensureAuthenticated middleware to protect routes:

app.get('/dashboard', ensureAuthenticated, (req, res) => {
  res.send(`Welcome to your dashboard, ${req.user.username}!`);
});

7. Conclusion

In this blog post, we’ve covered how to implement secure user authentication using Passport.js. We’ve:

  1. Set up a Node.js project with Express and Passport.

  2. Implemented local authentication with username and password.

  3. Added password hashing using bcrypt.

  4. Used sessions for persistent login.

  5. Protected routes with authentication middleware.

Passport.js is a powerful tool that simplifies authentication in Node.js applications. By following best practices like password hashing and session management, you can ensure your application remains secure.

For further reading, explore Passport.js’s documentation and other authentication strategies like OAuth and JWT.

Happy coding! 🚀