Passport.js β Authentication Middleware
After completing this topic
You will understand why Passport.js is necessary, be able to implement ID/password authentication using the Local Strategy, and explain the roles of serialization and deserialization.
Why use Passport.js?
In the previous topic, we implemented session-based authentication ourselves. While it works, the code becomes more complex as the number of authentication methods increases.
- ID/password login
- Google login
- GitHub login
- JWT token authentication
Implementing each of these separately would scatter authentication logic throughout the routes. Passport.js is an authentication middleware that solves this problem. It provides 500+ authentication strategies as plug-in modules.
Core Concept β Strategy Pattern
Passport.js uses the Strategy pattern. It separates "how to authenticate" into Strategy objects.
Passport.js (framework)
βββ Local Strategy β ID/password
βββ Google Strategy β Google OAuth
βββ GitHub Strategy β GitHub OAuth
βββ JWT Strategy β JSON Web TokenEach Strategy is an independent npm package. Install and use only what you need.
Installation
npm install passport passport-local express-sessionpassport: Core librarypassport-local: ID/password authentication strategyexpress-session: Session management (used internally by Passport)
Setting up the Local Strategy
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const app = express();
// Mock user database
const users = [
{ id: 1, username: 'alice', password: 'pass123', displayName: 'Alice Kim' },
{ id: 2, username: 'bob', password: 'pass456', displayName: 'Bob Lee' }
];
// 1. Register the Strategy
passport.use(new LocalStrategy(
(username, password, done) => {
const user = users.find(u => u.username === username);
if (!user) {
return done(null, false, { message: 'User not found' });
}
if (user.password !== password) {
return done(null, false, { message: 'Wrong password' });
}
return done(null, user);
}
));The three calling patterns for the done callback:
| Call | Meaning |
|---|---|
done(null, user) | Authentication successful. Pass the user object. |
done(null, false, {message}) | Authentication failed. User not found or incorrect password. |
done(err) | System error. Database connection failure, etc. |
Serialize / Deserialize
Storing the entire user object in the session wastes memory. Passport stores only the minimum identifying information (usually the ID) in the session and restores the user from that ID on each request.
// 2. Data to be stored in the session (once upon login)
passport.serializeUser((user, done) => {
done(null, user.id); // Store only user.id in the session
});
// 3. Restore the user from the session (on each request)
passport.deserializeUser((id, done) => {
const user = users.find(u => u.id === id);
done(null, user); // Set the entire user object in req.user
});On login: user object β serializeUser β store id=1 in session
On each request: id=1 from session β deserializeUser β restore user object β req.userBy querying the database in deserializeUser, you can store only the ID in the session while still having access to the latest user information on each request.
Connecting the Middleware
// 4. Configure middleware (order is important!)
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(session({
secret: process.env.SESSION_SECRET || 'dev-secret',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize()); // Initialize Passport
app.use(passport.session()); // Connect to the sessionThe order is important: express-session β passport.initialize() β passport.session(). If the order is incorrect, the session will not work properly.
Login / Logout Routes
// Login
app.post('/login',
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureMessage: true
})
);
// Or, if you want a JSON response
app.post('/api/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) return next(err);
if (!user) {
return res.status(401).json({ error: info.message });
}
req.logIn(user, (err) => {
if (err) return next(err);
res.json({ message: `Welcome, ${user.displayName}` });
});
})(req, res, next);
});
// Logout
app.post('/logout', (req, res) => {
req.logout((err) => {
if (err) return res.status(500).json({ error: 'Logout failed' });
res.json({ message: 'Logged out' });
});
});passport.authenticate('local') executes the LocalStrategy that you registered earlier. If successful, serializeUser is called, and the user ID is stored in the session.
Protected Routes
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({ error: 'Login required' });
}
app.get('/dashboard', ensureAuthenticated, (req, res) => {
res.json({
message: `Hello, ${req.user.displayName}`,
user: { id: req.user.id, username: req.user.username }
});
});req.isAuthenticated() is a method provided by Passport that returns true if there is a valid user in the session. req.user is the user object restored by deserializeUser.
Overall Flow Summary
1. Server starts
passport.use(LocalStrategy) β Register authentication method
passport.serializeUser() β Register session storage method
passport.deserializeUser() β Register session restoration method
2. POST /login
passport.authenticate('local') β Execute Strategy
β Success: serializeUser β Store ID in session β Respond
β Failure: 401 response
3. GET /dashboard (authentication required)
Check session cookie β deserializeUser β Set req.user
ensureAuthenticated β req.isAuthenticated() β true β Execute route
4. POST /logout
req.logout() β Remove user from session β RespondCommon Mistakes
| Mistake | Result | Solution |
|---|---|---|
Call passport.session() before app.use(session(...)) | Session is not initialized | express-session β passport.initialize() β passport.session() order |
Store the entire user object in serializeUser | Session size explodes | Store only user.id |
Ignore asynchronous errors in deserializeUser | Server crashes on authentication failure | Call done(err) |
Forget to call done() in the Strategy callback | Request is indefinitely pending | Verify that done() is called in all branches |
Key Takeaways
| Concept | Summary |
|---|---|
| Passport.js | Authentication middleware. Supports various authentication methods using the Strategy pattern |
| Strategy | Object that encapsulates the authentication method (Local, Google, JWT, etc.) |
| serializeUser | Determine the minimum information to be stored in the session (usually user.id) |
| deserializeUser | Restore the user object from the session ID on each request |
req.user | User object restored by deserializeUser |
req.isAuthenticated() | Check login status |
The value of Passport.js is "standardization of authentication logic." When switching from Local to Google OAuth, you only need to replace the Strategy. serialize/deserialize, middleware chain, req.user β the rest of the code remains the same.