Authentication with Passport.js
Passport.js is the most popular authentication middleware for Node.js, offering a flexible and modular approach to authentication. With over 500+ strategies, Passport simplifies implementing various authentication methods in Express applications.
Passport.js Overview
Passport provides a clean, structured way to handle authentication with these key features:
- Strategy-based: Pluggable authentication strategies (Local, OAuth, JWT, etc.)
- Middleware-driven: Integrates seamlessly with Express
- Session Support: Built-in session management with serialization
- Extensible: Easy to create custom strategies
- Community: Large ecosystem with 500+ authentication strategies
Key Concept: Passport uses "strategies" to authenticate users. Each strategy handles a specific authentication method (username/password, OAuth, JWT, etc.).
Authentication Strategies
Passport supports numerous authentication strategies:
// Common Passport Strategies
// 1. Local Strategy (username/password)
passport-local
// 2. JWT Strategy (JSON Web Tokens)
passport-jwt
// 3. OAuth Strategies
passport-google-oauth20
passport-facebook
passport-twitter
passport-github
// 4. Other Strategies
passport-oauth2
passport-saml
passport-openid
passport-ldap
Installing Passport
Install Passport and required strategies:
# Core Passport package
npm install passport
# For local username/password authentication
npm install passport-local bcryptjs
# For JWT authentication
npm install passport-jwt jsonwebtoken
# For session support
npm install express-session
Configuring Passport - Local Strategy
Set up Passport with username/password authentication:
// config/passport.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');
const User = require('../models/User');
// Configure Local Strategy
passport.use(new LocalStrategy(
{
usernameField: 'email', // Use email instead of username
passwordField: 'password'
},
async (email, password, done) => {
try {
// Find user by email
const user = await User.findOne({ email }).select('+password');
// Check if user exists
if (!user) {
return done(null, false, {
message: 'Incorrect email or password'
});
}
// Validate password
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return done(null, false, {
message: 'Incorrect email or password'
});
}
// Authentication successful
return done(null, user);
} catch (error) {
return done(error);
}
}
));
module.exports = passport;
Serialize and Deserialize User
Configure how user data is stored in and retrieved from sessions:
// config/passport.js (continued)
// Serialize user - Store user ID in session
passport.serializeUser((user, done) => {
done(null, user.id);
});
// Deserialize user - Retrieve user from database using ID
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id).select('-password');
done(null, user);
} catch (error) {
done(error);
}
});
Session Management: serializeUser stores only the user ID in the session to keep it lightweight. deserializeUser fetches the full user object on subsequent requests.
Integrating with Express
Set up Express with Passport and sessions:
// app.js
const express = require('express');
const session = require('express-session');
const passport = require('./config/passport');
const app = express();
// Body parser
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Session configuration
app.use(session({
secret: process.env.SESSION_SECRET || 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only in production
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
// Routes
app.use('/api/auth', require('./routes/auth'));
module.exports = app;
Session-Based Authentication Routes
Implement login, register, and logout routes:
// routes/auth.js
const express = require('express');
const router = express.Router();
const passport = require('passport');
const bcrypt = require('bcryptjs');
const User = require('../models/User');
// Register new user
router.post('/register', async (req, res) => {
try {
const { name, email, password } = req.body;
// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({
success: false,
message: 'User already exists'
});
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Create user
const user = await User.create({
name,
email,
password: hashedPassword
});
// Auto-login after registration
req.login(user, (err) => {
if (err) {
return res.status(500).json({
success: false,
message: 'Registration successful but login failed'
});
}
res.status(201).json({
success: true,
user: {
id: user._id,
name: user.name,
email: user.email
}
});
});
} catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
});
// Login using Passport Local Strategy
router.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
return res.status(500).json({
success: false,
message: err.message
});
}
if (!user) {
return res.status(401).json({
success: false,
message: info.message || 'Authentication failed'
});
}
// Establish session
req.login(user, (err) => {
if (err) {
return res.status(500).json({
success: false,
message: 'Login failed'
});
}
res.json({
success: true,
user: {
id: user._id,
name: user.name,
email: user.email
}
});
});
})(req, res, next);
});
// Logout
router.post('/logout', (req, res) => {
req.logout((err) => {
if (err) {
return res.status(500).json({
success: false,
message: 'Logout failed'
});
}
res.json({
success: true,
message: 'Logged out successfully'
});
});
});
// Get current user
router.get('/me', (req, res) => {
if (!req.isAuthenticated()) {
return res.status(401).json({
success: false,
message: 'Not authenticated'
});
}
res.json({
success: true,
user: req.user
});
});
module.exports = router;
Authentication Middleware
Create middleware to protect routes:
// middleware/auth.js
// Check if user is authenticated
exports.isAuthenticated = (req, res, next) => {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({
success: false,
message: 'Please login to access this resource'
});
};
// Check if user has specific role
exports.authorizeRoles = (...roles) => {
return (req, res, next) => {
if (!req.isAuthenticated()) {
return res.status(401).json({
success: false,
message: 'Please login first'
});
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
message: `Role ${req.user.role} is not allowed to access this resource`
});
}
next();
};
};
// Usage in routes
// router.get('/profile', isAuthenticated, getProfile);
// router.delete('/admin/users/:id', authorizeRoles('admin'), deleteUser);
Passport JWT Strategy
Implement stateless JWT authentication with Passport:
// config/passport-jwt.js
const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../models/User');
const opts = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET || 'your-jwt-secret'
};
passport.use(new JwtStrategy(opts, async (jwt_payload, done) => {
try {
// Find user by ID from JWT payload
const user = await User.findById(jwt_payload.userId);
if (user) {
return done(null, user);
}
return done(null, false);
} catch (error) {
return done(error, false);
}
}));
module.exports = passport;
JWT Routes with Passport
// routes/auth-jwt.js
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const passport = require('../config/passport-jwt');
const User = require('../models/User');
// Generate JWT token
const generateToken = (user) => {
return jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET || 'your-jwt-secret',
{ expiresIn: '7d' }
);
};
// Login with JWT
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email }).select('+password');
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
const token = generateToken(user);
res.json({
success: true,
token,
user: {
id: user._id,
name: user.name,
email: user.email
}
});
} catch (error) {
res.status(500).json({
success: false,
message: error.message
});
}
});
// Protected route using Passport JWT
router.get('/profile',
passport.authenticate('jwt', { session: false }),
(req, res) => {
res.json({
success: true,
user: req.user
});
}
);
module.exports = router;
OAuth with Passport (Google Example)
Implement social login with Google OAuth:
// npm install passport-google-oauth20
// config/passport-google.js
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const User = require('../models/User');
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/api/auth/google/callback'
},
async (accessToken, refreshToken, profile, done) => {
try {
// Check if user exists
let user = await User.findOne({ googleId: profile.id });
if (user) {
return done(null, user);
}
// Create new user
user = await User.create({
googleId: profile.id,
name: profile.displayName,
email: profile.emails[0].value,
avatar: profile.photos[0].value
});
done(null, user);
} catch (error) {
done(error, null);
}
}));
// routes/auth-oauth.js
router.get('/google',
passport.authenticate('google', {
scope: ['profile', 'email']
})
);
router.get('/google/callback',
passport.authenticate('google', {
failureRedirect: '/login',
session: false
}),
(req, res) => {
// Generate JWT token
const token = generateToken(req.user);
// Redirect to frontend with token
res.redirect(`${process.env.FRONTEND_URL}/auth/success?token=${token}`);
}
);
OAuth Setup: You need to register your application with Google OAuth Console to get CLIENT_ID and CLIENT_SECRET. Set up authorized redirect URIs properly.
Complete Passport Setup
// app.js - Complete configuration
const express = require('express');
const session = require('express-session');
const MongoStore = require('connect-mongo');
// Import passport configurations
require('./config/passport'); // Local strategy
require('./config/passport-jwt'); // JWT strategy
require('./config/passport-google'); // Google OAuth
const passport = require('passport');
const app = express();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Session with MongoDB store
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI,
touchAfter: 24 * 3600 // Lazy session update
}),
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days
}
}));
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
// Routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/auth-jwt', require('./routes/auth-jwt'));
app.use('/api/auth-oauth', require('./routes/auth-oauth'));
module.exports = app;
Session Store: For production, always use a proper session store (MongoDB, Redis, PostgreSQL) instead of the default memory store, which doesn't scale and loses data on server restart.
Practice Exercise
Build a complete authentication system using Passport.js:
- Set up Passport with Local Strategy for email/password authentication
- Implement user registration with password hashing
- Create login and logout endpoints
- Configure session management with MongoDB store
- Add JWT Strategy for stateless API authentication
- Implement protected routes with authentication middleware
- Add role-based authorization (user, admin)
- Bonus: Implement Google OAuth login