Express Authentication Middleware
Authentication is a crucial aspect of web development, especially when building applications that handle user data or provide personalized experiences. In Express.js applications, authentication middleware serves as a guardian that protects your routes and ensures only authorized users can access specific parts of your application.
What is Authentication Middleware?
Authentication middleware in Express is code that runs between the request and your route handlers to verify if a user is authenticated before allowing access to protected resources. It intercepts the incoming request, performs authentication checks, and then either allows the request to proceed or redirects/returns an error.
Why Use Authentication Middleware?
- Route Protection: Keep unauthorized users away from sensitive routes
- Code Reusability: Write authentication logic once and apply it to multiple routes
- Separation of Concerns: Keep authentication logic separate from your business logic
- Maintainability: Easier to update authentication requirements across your application
Basic Authentication Middleware
Let's start by building a simple authentication middleware function from scratch:
function isAuthenticated(req, res, next) {
// Check if user is authenticated
if (req.session && req.session.user) {
// User is authenticated, proceed to the next middleware
return next();
}
// User is not authenticated
return res.status(401).json({ message: 'Not authorized. Please log in.' });
}
How to Use the Middleware
You can apply this middleware to individual routes or groups of routes:
const express = require('express');
const app = express();
// Apply to a single route
app.get('/profile', isAuthenticated, (req, res) => {
res.send(`Welcome to your profile, ${req.session.user.name}!`);
});
// Apply to all routes in a router
const adminRouter = express.Router();
adminRouter.use(isAuthenticated);
adminRouter.get('/dashboard', (req, res) => {
res.send('Admin dashboard');
});
adminRouter.get('/users', (req, res) => {
res.send('User management');
});
app.use('/admin', adminRouter);
Creating a Complete Authentication System
Let's build a more robust authentication system with user registration, login, and protected routes:
First, let's set up our Express app with the necessary middleware:
const express = require('express');
const session = require('express-session');
const bcrypt = require('bcrypt');
const app = express();
// Middleware for parsing request bodies
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Session middleware
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: { secure: process.env.NODE_ENV === 'production', maxAge: 3600000 } // 1 hour
}));
// In-memory user store (in a real app, use a database)
const users = [];
User Registration
app.post('/register', async (req, res) => {
try {
const { username, password, email } = req.body;
// Check if user already exists
if (users.find(user => user.username === username)) {
return res.status(400).json({ message: 'Username already taken' });
}
// Hash the password
const hashedPassword = await bcrypt.hash(password, 10);
// Create new user
const newUser = {
id: users.length + 1,
username,
email,
password: hashedPassword
};
// Add to our "database"
users.push(newUser);
res.status(201).json({ message: 'User registered successfully' });
} catch (error) {
res.status(500).json({ message: 'Error registering user', error: error.message });
}
});
User Login
app.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
// Find the user
const user = users.find(user => user.username === username);
if (!user) {
return res.status(400).json({ message: 'Invalid username or password' });
}
// Compare passwords
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return res.status(400).json({ message: 'Invalid username or password' });
}
// Create session
req.session.user = {
id: user.id,
username: user.username,
email: user.email
};
res.json({ message: 'Logged in successfully', user: req.session.user });
} catch (error) {
res.status(500).json({ message: 'Error logging in', error: error.message });
}
});
Logout Route
app.get('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ message: 'Could not log out' });
}
res.json({ message: 'Logged out successfully' });
});
});
Auth Middleware
Now we'll create our authentication middleware:
function isAuthenticated(req, res, next) {
if (req.session && req.session.user) {
return next();
}
res.status(401).json({ message: 'Authentication required' });
}
// For role-based access control
function hasRole(role) {
return (req, res, next) => {
if (req.session && req.session.user && req.session.user.role === role) {
return next();
}
res.status(403).json({ message: 'Access denied: insufficient permissions' });
};
}
Protected Routes
// Protected route
app.get('/profile', isAuthenticated, (req, res) => {
res.json({
message: 'This is your profile',
user: req.session.user
});
});
// Admin-only route (assuming we added roles to our users)
app.get('/admin', isAuthenticated, hasRole('admin'), (req, res) => {
res.json({ message: 'Admin dashboard' });
});
JWT Authentication Middleware
Session-based authentication works great for traditional web applications, but for APIs and modern single-page applications, JSON Web Tokens (JWT) are often preferred.
Let's implement JWT authentication middleware:
const jwt = require('jsonwebtoken');
// Secret key for signing the token
const JWT_SECRET = 'your-jwt-secret';
// Middleware to verify JWT
function verifyToken(req, res, next) {
// Get token from header
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ message: 'No token provided' });
}
// Format should be "Bearer [token]"
const parts = authHeader.split(' ');
if (parts.length !== 2 || parts[0] !== 'Bearer') {
return res.status(401).json({ message: 'Token error' });
}
const token = parts[1];
try {
// Verify token
const decoded = jwt.verify(token, JWT_SECRET);
// Add user info to request
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ message: 'Invalid token' });
}
}
JWT Login Route
app.post('/api/login', async (req, res) => {
try {
const { username, password } = req.body;
// Find the user
const user = users.find(user => user.username === username);
if (!user) {
return res.status(400).json({ message: 'Invalid username or password' });
}
// Compare passwords
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return res.status(400).json({ message: 'Invalid username or password' });
}
// Create payload for JWT
const payload = {
id: user.id,
username: user.username,
email: user.email
};
// Generate token
const token = jwt.sign(
payload,
JWT_SECRET,
{ expiresIn: '1h' } // Token expires in 1 hour
);
res.json({ token });
} catch (error) {
res.status(500).json({ message: 'Error logging in', error: error.message });
}
});
Protected API Routes with JWT
// Protected API route
app.get('/api/me', verifyToken, (req, res) => {
res.json({ user: req.user });
});
// Resource API route
app.get('/api/resources', verifyToken, (req, res) => {
res.json({
resources: [
{ id: 1, name: 'Resource 1', ownerId: req.user.id },
{ id: 2, name: 'Resource 2', ownerId: req.user.id }
]
});
});
Using Passport.js for Authentication
Passport is a popular authentication middleware for Node.js that supports various authentication strategies.
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
// Initialize passport
app.use(passport.initialize());
app.use(passport.session());
// Configure passport local strategy
passport.use(new LocalStrategy(
async (username, password, done) => {
try {
// Find user
const user = users.find(user => user.username === username);
if (!user) {
return done(null, false, { message: 'Incorrect username' });
}
// Check password
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return done(null, false, { message: 'Incorrect password' });
}
return done(null, user);
} catch (error) {
return done(error);
}
}
));
// Serialize and deserialize user for session management
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
const user = users.find(user => user.id === id);
done(null, user);
});
Login Route with Passport
app.post('/login', passport.authenticate('local'), (req, res) => {
res.json({
message: 'Logged in successfully',
user: {
id: req.user.id,
username: req.user.username,
email: req.user.email
}
});
});
Protected Routes with Passport
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({ message: 'Please log in to access this resource' });
}
app.get('/profile', ensureAuthenticated, (req, res) => {
res.json({ user: req.user });
});
Real-world Example: E-commerce API
Here's an example of how authentication middleware might be used in a real e-commerce API:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
// JWT middleware
function authenticate(req, res, next) {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) throw new Error('No token provided');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ message: 'Authentication failed' });
}
}
// Check if user has admin privileges
function isAdmin(req, res, next) {
if (req.user && req.user.role === 'admin') {
return next();
}
return res.status(403).json({ message: 'Admin access required' });
}
// Public routes
app.get('/products', (req, res) => {
res.json({ products: [/* product list */] });
});
// Protected customer routes
app.get('/orders', authenticate, (req, res) => {
// Return only orders for the authenticated user
const userOrders = ordersDB.filter(order => order.userId === req.user.id);
res.json({ orders: userOrders });
});
app.post('/orders', authenticate, (req, res) => {
// Create a new order for this user
const newOrder = {
...req.body,
userId: req.user.id,
date: new Date()
};
// Save order to database
res.status(201).json({ order: newOrder });
});
// Admin-only routes
app.get('/admin/orders', authenticate, isAdmin, (req, res) => {
// Return all orders for admin
res.json({ orders: ordersDB });
});
app.get('/admin/users', authenticate, isAdmin, (req, res) => {
// Return all users for admin
res.json({ users: usersDB });
});
Summary
Express authentication middleware is a powerful tool for securing your web applications and APIs. In this guide, we've covered:
- Creating basic authentication middleware from scratch
- Implementing session-based authentication
- Building JWT authentication for APIs
- Using Passport.js for flexible authentication strategies
- Real-world application in an e-commerce API
By implementing authentication middleware in your Express applications, you can ensure that sensitive data and operations are protected while still providing a seamless experience for authorized users.
Additional Resources
- Express.js official documentation
- Passport.js documentation
- JWT.io - Learn about JSON Web Tokens
- OWASP Authentication Cheat Sheet
Exercises
- Modify the basic authentication middleware to check for specific user roles
- Implement a "remember me" feature that extends session lifetime
- Create a password reset flow with email verification
- Add two-factor authentication using a library like Speakeasy
- Implement OAuth authentication with a provider like Google or GitHub using Passport
Remember: Security is critical in authentication systems. Always use HTTPS in production, securely store passwords with strong hashing algorithms, and keep your dependencies updated to protect against vulnerabilities.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)