Passport.js is the most popular authentication middleware for Node.js, supporting over 500 strategies with flexible, modular design.
It simplifies the implementation of both complex and straightforward authentication flows, ranging from local logins to OAuth with social providers.
This guide covers configuring multiple strategies, securing routes, and best practices to build robust, user-friendly authentication systems in your Express.js app.
Prerequisites
Before diving into the implementation details, make sure you have a recent version of Node.js and npm installed on your development machine. This guide assumes you are familiar with the basics of Express.js and fundamental web authentication concepts, such as sessions, cookies, and HTTP status codes.
Step 1 — Creating a basic Express server
Building secure authentication requires starting with a solid foundation. You'll begin with a minimal Express server and progressively add authentication capabilities.
Create your project directory and navigate into it:
Initialize your Node.js project:
Configure your project to use ES modules:
Install Express to get started:
Create a basic server file called app.js:
Set up your development script for automatic restarts:
Start your server:
You should see:
Now visit http://localhost:3000 in the browser of your choice.
You should see the JSON response with your "Hello World" message, confirming that Express is working correctly.
This basic setup provides us with a working Express server that can respond with JSON. The server runs on port 3000 and handles GET requests with a simple message, serving as the foundation for building our authentication system.
Step 2 — Adding essential middleware
Authentication systems require several middleware components for request parsing, logging, and handling cross-origin requests. You'll add these foundational pieces before implementing Passport.js to ensure your server can properly handle authentication requests.
Install the additional middleware packages:
These middleware components serve specific purposes in your authentication system:
morganprovides HTTP request logging for monitoring and debuggingcorsenables Cross-Origin Resource Sharing for API accessibility
Update your app.js to include the essential middleware:
In the updated code, you are building a strong middleware foundation for authentication.
Morgan logging displays all incoming requests in your terminal, making it significantly easier to debug authentication flows as they occur.
The CORS configuration allows credentials to be sent explicitly across different origins, which becomes essential when implementing session-based authentication.
Additionally, you're enabling both JSON and URL-encoded request parsing, ensuring your server can handle authentication requests whether they come as JSON payloads from API clients or form submissions from web interfaces.
Save your changes, and you'll notice Morgan now logs each request to your terminal. Visit http://localhost:3000 again and you should see a log entry like:
This confirms your middleware stack is configured correctly and ready for authentication integration.
Step 3 — Installing Passport.js, sessions, and creating user storage
Authentication systems require three foundational components working together: session management for maintaining user state, Passport.js for handling authentication strategies, and a user storage system. You'll set up all three components in this step to create a cohesive authentication foundation.
Install all the required dependencies for authentication and database functionality:
These packages work together to provide complete authentication capabilities. Express-session maintains user sessions across requests, while passport and passport-local handle the authentication logic.
Sequelize and sqlite3 provide database functionality, bcrypt ensures secure password hashing, and uuid generates unique user identifiers.
Add session configuration and Passport initialization to your server:
In this setup, you're configuring the session middleware to create secure user sessions with optimized settings.
The resave: false and saveUninitialized: false options prevent unnecessary session writes, boosting performance. Passport initialization sets up the authentication framework and integrates seamlessly with your existing session system.
You've also updated the root route to display authentication status using Passport's req.isAuthenticated() method.
Now create the directory structure for your database and models:
Create your database configuration file:
This configuration establishes your SQLite database connection, creating the database file in a data directory and disabling query logging for cleaner output.
Create a secure User model with automatic password hashing:
This model defines your user table with UUID primary keys, automatic password hashing using bcrypt hooks, and a secure password validation method for authentication.
The User model includes automatic password hashing through Sequelize hooks, ensuring passwords are never stored in plain text. The validatePassword method provides secure password comparison using bcrypt's built-in timing-safe comparison.
Finally, integrate the database with your Express application:
This updated app imports your database configuration and User model, then initializes the database when the server starts, creating tables automatically through Sequelize's sync method.
Save your changes and you should see the complete initialization:
Your authentication foundation is now complete with sessions, Passport integration, and secure user storage all working together.
Visit http://localhost:3000 to confirm everything is working. You should see the authentication status in the response:
The authenticated: false status is expected since you haven't implemented login functionality yet. This confirms that your Express server, sessions, Passport.js initialization, and database synchronization are all working correctly together.
Step 4 — Configuring Passport local strategy
With your database and Passport foundation ready, you need to configure Passport's Local Strategy to handle username/password authentication. This involves telling Passport how to verify credentials and manage user sessions.
Create a configuration file for your Passport strategies:
This strategy configuration defines how Passport verifies user credentials. It looks up users by username and uses the validatePassword method you created earlier for secure password comparison.
Now add session serialization to handle user sessions:
Session serialization stores only the user ID in the session for efficiency, while deserialization retrieves the full user object when needed. This keeps sessions lightweight while maintaining complete user access throughout your application.
Import this configuration in your main app:
This import ensures your Passport strategy configuration is loaded when the application starts, making the Local Strategy available for authentication requests.
Save your changes and restart your server. You should see the same output as before, but now Passport is configured with your Local Strategy and ready to authenticate users:
Your Passport configuration is now complete and ready to handle user authentication. In the next step, you'll create the authentication routes that users will interact with.
Step 5 — Creating authentication routes
Now that Passport is configured with your Local Strategy, you'll create the actual authentication endpoints that users will interact with. You'll start with registration and login routes to handle user account creation and authentication.
Create a routes directory and authentication router:
This creates the foundation for your authentication routes using Express Router, which allows you to organize routes in separate modules.
Now add the registration endpoint:
This registration endpoint creates new users while preventing duplicates and automatically hashing passwords through your Sequelize hook. The response excludes the password for security reasons.
Add the login endpoint that uses your Passport Local Strategy:
The login endpoint uses Passport's authenticate method with a custom callback, giving you complete control over the authentication response format and error handling.
Connect your authentication routes to the main application:
This mounts your authentication routes under the /auth path, making them accessible at /auth/register and /auth/login.
Save your changes, and you should see the familiar server startup:
Your authentication routes are now ready for testing. In the next step, you'll test the registration and login functionality using command-line tools.
Step 6 — Testing user registration and login
With your authentication routes in place, you can now test the complete registration and login functionality. You'll use curl commands to simulate client requests, though you can also use Postman or any other API testing tool for a more visual experience.
First, test user registration by creating a new account:
You should receive a successful registration response:
If you prefer using Postman, create a POST request to http://localhost:3000/auth/register with the same JSON body but a different username:
The response confirms that your user was created successfully with a unique UUID and that the password was automatically hashed before storage.
Now test the login functionality using the credentials you just created:
The -c cookies.txt flag saves the session cookie for subsequent requests. You should see a successful login response:
This confirms that your Passport Local Strategy successfully authenticated the user and created a session.
Test the authentication status by visiting the root route with your saved session cookie:
You should now see that the authentication status has changed:
The authenticated: true status confirms that your session is working correctly and that Passport recognizes the logged-in user.
Now test error handling by attempting to register a duplicate user:
You should receive an error response:
Test invalid login credentials to verify authentication security:
You should see an authentication failure:
These tests confirm that your registration prevents duplicates, your login authentication works correctly with valid credentials, sessions are maintained properly, and invalid credentials are rejected securely.
Whether you use curl or Postman, your authentication system is now fully functional and ready for additional features.
Step 7 — Adding logout and protected routes
Complete your authentication system by implementing logout functionality and demonstrating how to protect routes that require user authentication. These features round out the core authentication workflow and show how to secure different parts of your application.
Add logout and profile routes to your authentication router:
The logout endpoint uses Passport's logout method to destroy the user session and clear authentication state. The profile endpoint demonstrates route protection by checking authentication status before returning sensitive data.
Test the logout functionality with your existing session:
You should see a successful logout response:
Verify that the session has been destroyed by checking the authentication status:
The authentication status should now be false:
Test the protected profile route without authentication to see the protection in action:
You should receive an authentication error:
Now log back in and test the protected route with valid authentication:
Access the profile route with your authenticated session:
You should now see the protected profile data:
This demonstrates how to implement route-level authentication protection in your Express application. The pattern of checking req.isAuthenticated() can be applied to any route that requires user authentication, ensuring that only logged-in users can access protected resources.
Your authentication system now includes complete user registration, login, logout, and route protection functionality.
Final thoughts
You've successfully built a complete authentication system using Passport.js and Express with secure password hashing, session management, protected routes, and comprehensive error handling.
The modular structure makes it easy to extend with additional features like password reset, email verification, or OAuth strategies. Explore the official Passport.js documentation and strategy packages to learn about additional authentication methods.
For production, consider implementing rate limiting, HTTPS enforcement, secure session storage, and comprehensive logging. Your authentication foundation is now ready to secure real-world applications.