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:
What is Passport.js?
Setting Up a Node.js Project
Implementing Local Authentication
Adding Password Hashing with bcrypt
Using Sessions for Persistent Login
Protecting Routes with Middleware
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:
Set up a Node.js project with Express and Passport.
Implemented local authentication with username and password.
Added password hashing using bcrypt.
Used sessions for persistent login.
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! 🚀