How to configure Express.js middleware for authentication
Set up Passport.js and JWT tokens to protect routes in a Node.js application using Express. Follow these steps to implement secure login and session management.
This guide shows you how to configure Passport.js and JSON Web Token (JWT) middleware to protect routes in an Express application. The steps target Node.js 20.x, Express 4.18.x, and Passport 0.7.x on Linux. You will implement local strategy for email/password login and generate signed tokens for stateless API access.
Prerequisites
- Node.js 20.x installed and available in your PATH.
- Express 4.18.x project initialized with
npm init. - Environment variables set for database connection and JWT secret.
- Access to a package manager like npm or yarn.
Step 1: Install dependencies
Install the required packages for authentication, token handling, and local strategy. Run this command in your project directory to add the necessary modules.
npm install express passport passport-local passport-jwt jsonwebtoken bcryptjs dotenv
You will see a progress bar and a list of installed packages ending with a success message.
added 145 packages in 8s
Step 2: Create environment configuration
Create a .env file to store sensitive values outside your source code. Define the JWT secret and database connection string here.
echo "JWT_SECRET=your-super-secret-key-1234567890abcdef" > .env
echo "DB_HOST=localhost" >> .env
echo "DB_USER=root" >> .env
echo "DB_PASS=password" >> .env
echo "DB_NAME=myapp" >> .env
Ensure the file permissions are restricted so only your user can read it.
chmod 600 .env
Step 3: Initialize Passport in Express
Import Passport and initialize it in your main application file. This sets up the middleware chain that will handle authentication logic.
const express = require('express');
const passport = require('passport');
const app = express();
app.use(passport.initialize());
app.use(express.json());
module.exports = app;
Save this as app.js. The application now accepts Passport requests but has no strategies configured yet.
Step 4: Configure Local Strategy for Login
Add the local strategy to handle email and password validation. You must import the strategy and tell Passport which module to use for hashing passwords.
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');
passport.use(new LocalStrategy({
usernameField: 'email'
},
async (email, password, done) => {
try {
// Simulate user lookup in database
const user = await getUserByEmail(email);
if (!user) return done(null, false, { message: 'Invalid credentials' });
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) return done(null, false, { message: 'Invalid credentials' });
return done(null, user);
} catch (err) {
return done(err);
}
}
));
Replace getUserByEmail with your actual database query function. This strategy verifies credentials and returns the user object on success.
Step 5: Configure JWT Strategy for API Tokens
Set up the JWT strategy to validate incoming tokens. This allows stateless authentication for mobile clients or microservices.
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const opts = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET
};
passport.use(
new JwtStrategy(opts, async (jwtPayload, done) => {
try {
const user = await getUserById(jwtPayload.id);
if (!user) return done(null, false);
return done(null, user);
} catch (err) {
return done(err, false);
}
})
);
Store this configuration in a separate file like strategies.js to keep your main app file clean. Import and use the strategies before defining routes.
Step 6: Protect Routes with Middleware
Create a middleware function that authenticates requests using Passport. Attach it to specific routes or groups of routes to enforce security.
const authenticate = passport.authenticate('local', { session: false });
app.post('/login', authenticate, (req, res) => {
const { user } = req.auth;
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
});
app.get('/protected', authenticate, (req, res) => {
res.json({ user: req.user });
});
The authenticate middleware checks credentials or token validity before allowing access to the route handler. Unauthenticated requests receive a 401 error automatically.
Verify the installation
Start your server and test the login endpoint with a valid email and password. You should receive a JSON response containing a JWT token.
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"password123"}'
Expected output:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Then send a request to the protected route with the token in the Authorization header:
curl http://localhost:3000/protected \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
You should see the authenticated user data in the response. If you omit the token, the server returns a 401 Unauthorized status code.
Troubleshooting
If you encounter errors, check the following common issues. First, verify that your .env file is loaded correctly by logging the secret variable.
console.log(process.env.JWT_SECRET);
Ensure the output matches the value you set. If it is undefined, your environment file is not being read. Next, confirm that Passport strategies are registered before you define routes. Run this check in your app file:
console.log('Strategies registered:', Object.keys(passport.strategies));
The output must include local and jwt. If they are missing, move your strategy definitions above the route definitions. Finally, ensure your bcrypt comparison does not throw an error by catching exceptions in your strategy function. If you see stack traces in the console, review your database query logic and error handling within the strategy callbacks.