Node.js & Express

Authentication with Passport.js

20 min Lesson 15 of 40

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:

  1. Set up Passport with Local Strategy for email/password authentication
  2. Implement user registration with password hashing
  3. Create login and logout endpoints
  4. Configure session management with MongoDB store
  5. Add JWT Strategy for stateless API authentication
  6. Implement protected routes with authentication middleware
  7. Add role-based authorization (user, admin)
  8. Bonus: Implement Google OAuth login