Authentication is a critical component of any web application, ensuring that users are who they claim to be. It provides the foundation for controlling access to resources, protecting sensitive data, and maintaining the integrity of your application. In Express.js, authentication is typically implemented using middleware, which acts as a gatekeeper, verifying user credentials before granting access to protected routes.
This chapter will cover everything you need to know about building and implementing authentication middleware in Express.js, from the basics to advanced techniques. We’ll explore various authentication strategies, how to protect routes, and best practices for security. By the end of this chapter, you’ll have a comprehensive understanding of how to secure your Express.js applications.
Middleware in Express.js is a function that executes during the lifecycle of an HTTP request. Middleware functions have access to the request (req
), response (res
), and the next
function, which passes control to the next middleware in the stack.
Authentication middleware specifically checks whether a user is authenticated (logged in) before allowing them to access certain routes or resources.
Authentication middleware is responsible for:
Authentication middleware centralizes the logic for checking user identity, making it easier to manage and update. It also ensures that protected routes are consistently enforced across your application.
Let’s start with a simple example of authentication middleware that checks if a user is logged in by verifying the presence of a session or a token.
app.js
const express = require('express');
const app = express();
const port = 3000;
// Simple authentication middleware
function isAuthenticated(req, res, next) {
if (req.session && req.session.user) {
return next(); // User is authenticated, proceed to the next middleware or route handler
} else {
return res.status(401).send('You are not authenticated');
}
}
// Route that does not require authentication
app.get('/public', (req, res) => {
res.send('This is a public route accessible to everyone.');
});
// Route that requires authentication
app.get('/protected', isAuthenticated, (req, res) => {
res.send('This is a protected route. You are authenticated.');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
req.session.user
exists. If it does, the user is authenticated, and the middleware calls next()
to proceed. If not, it returns a 401 Unauthorized
response./public
is accessible to everyone without authentication./protected
is accessible only to authenticated users./public
returns:
This is a public route accessible to everyone.
2.Accessing /protected
without authentication returns:
You are not authenticated
You can apply the isAuthenticated
middleware to multiple routes or even group routes that require authentication.
app.js
// Grouping protected routes
app.use('/admin', isAuthenticated);
app.get('/admin/dashboard', (req, res) => {
res.send('Welcome to the admin dashboard.');
});
app.get('/admin/settings', (req, res) => {
res.send('Admin settings page.');
});
app.use(‘/admin’, isAuthenticated): Applies the isAuthenticated
middleware to all routes under the /admin
path.
Accessing /admin/dashboard
or /admin/settings
without authentication returns:
You are not authenticated
JWT is a popular method for implementing token-based authentication. JWTs are compact, URL-safe tokens that can be verified and trusted because they are digitally signed.
First, install the required packages:
npm install jsonwebtoken
app.js
const jwt = require('jsonwebtoken');
// Secret key for signing tokens
const JWT_SECRET = 'your_jwt_secret';
// Middleware to verify JWT
function verifyToken(req, res, next) {
const token = req.headers['authorization'];
if (!token) {
return res.status(403).send('A token is required for authentication');
}
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).send('Invalid token');
}
req.user = decoded;
next();
});
}
// Route to login and receive a token
app.post('/login', (req, res) => {
// Assume a valid user for simplicity
const user = { id: 1, username: 'JohnDoe' };
const token = jwt.sign(user, JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
});
// Protected route
app.get('/dashboard', verifyToken, (req, res) => {
res.send(`Welcome to your dashboard, ${req.user.username}`);
});
Authorization
header, verifies it using jwt.verify()
, and adds the decoded user information to req.user
./dashboard
is accessible only with a valid JWT./login
returns a JWT token./dashboard
with the token in the Authorization
header returns
Welcome to your dashboard, JohnDoe
Passport.js is a powerful authentication middleware for Node.js that supports a wide range of authentication strategies, including local username/password, OAuth, OpenID, and more.
First, install the required packages:
npm install passport passport-local express-session
app.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
// In-memory user store (for example purposes only)
const users = [{ id: 1, username: 'JohnDoe', password: 'password123' }];
// Configure passport-local to use user store
passport.use(new LocalStrategy((username, password, done) => {
const user = users.find(u => u.username === username);
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (user.password !== password) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
}));
// Serialize user to store in session
passport.serializeUser((user, done) => {
done(null, user.id);
});
// Deserialize user from session
passport.deserializeUser((id, done) => {
const user = users.find(u => u.id === id);
done(null, user);
});
// Initialize passport and session middleware
app.use(session({ secret: 'your_secret_key', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
// Login route using passport-local strategy
app.post('/login', passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: true
}));
// Protected route
app.get('/dashboard', (req, res) => {
if (req.isAuthenticated()) {
res.send(`Welcome to your dashboard, ${req.user.username}`);
} else {
res.redirect('/login');
}
});
/dashboard
; on failure, they are redirected back to /login
./login
with valid credentials redirects to /dashboard
./dashboard
without logging in redirects to /login
.OAuth is a widely used protocol for token-based authorization, allowing users to log in using third-party services like Google, Facebook, or GitHub.
Let’s demonstrate how to use Google OAuth for authentication.
app.js
npm install passport-google-oauth20
const GoogleStrategy = require('passport-google-oauth20').Strategy;
// Configure the Google strategy
passport.use(new GoogleStrategy({
clientID: 'your_google_client_id',
clientSecret: 'your_google_client_secret',
callbackURL: '/auth/google/callback'
}, (accessToken, refreshToken, profile, done) => {
// Here you would find or create a user in your database
const user = { id: profile.id, username: profile.displayName };
return done(null, user);
}));
// Route to start the OAuth flow
app.get('/auth/google', passport.authenticate('google', { scope: ['profile'] }));
// OAuth callback route
app.get('/auth/google/callback', passport.authenticate('google', {
successRedirect: '/dashboard',
failureRedirect: '/login'
}));
/dashboard
on success./auth/google
redirects to Google’s login page./dashboard
.Authentication is a fundamental aspect of web application security, and middleware in Express.js provides a flexible way to enforce it. From simple session-based authentication to advanced strategies like JWTs and OAuth with Passport.js, Express.js offers powerful tools to implement and manage authentication in your applications.Happy coding !❤️