CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-passport

Simple, unobtrusive authentication for Node.js

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

session-management.mddocs/

Session Management

User session handling including login/logout operations and user serialization/deserialization for persistent authentication state across requests. Sessions allow users to remain logged in across multiple requests.

Capabilities

User Serialization

Configure how user objects are serialized into and deserialized from the session.

/**
 * Register function to serialize user objects into the session (registration form)
 * @param {Function} fn - Serialization function (user, done) => void or (user, req, done) => void
 * @returns {Authenticator} Returns this for chaining
 */
passport.serializeUser(fn);

/**
 * Execute user serialization (internal execution form)
 * @param {Object} user - User object to serialize
 * @param {Object} req - Express request object
 * @param {Function} done - Callback function (err, serializedData) => void
 */
passport.serializeUser(user, req, done);

/**
 * Register function to deserialize user objects from the session (registration form)
 * @param {Function} fn - Deserialization function (id, done) => void or (id, req, done) => void
 * @returns {Authenticator} Returns this for chaining
 */
passport.deserializeUser(fn);

/**
 * Execute user deserialization (internal execution form)
 * @param {any} obj - Serialized user data from session
 * @param {Object} req - Express request object
 * @param {Function} done - Callback function (err, user) => void
 */
passport.deserializeUser(obj, req, done);

Usage Examples:

// Basic user serialization (store user ID)
passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser((id, done) => {
  User.findById(id, (err, user) => {
    done(err, user);
  });
});

// Store multiple user properties
passport.serializeUser((user, done) => {
  done(null, {
    id: user.id,
    role: user.role,
    lastLogin: user.lastLogin
  });
});

passport.deserializeUser((sessionData, done) => {
  User.findById(sessionData.id, (err, user) => {
    if (err) return done(err);
    if (!user) return done(null, false);
    
    // Update last login tracking
    user.lastLogin = sessionData.lastLogin;
    done(null, user);
  });
});

// Promise-based serialization (modern approach)
passport.serializeUser(async (user, done) => {
  try {
    done(null, user.id);
  } catch (err) {
    done(err);
  }
});

passport.deserializeUser(async (id, done) => {
  try {
    const user = await User.findById(id);
    done(null, user);
  } catch (err) {
    done(err);
  }
});

Request Authentication Methods

Methods added to HTTP request objects for managing user authentication state.

/**
 * Log user into request and session
 * @param {Object} user - User object to log in
 * @param {Object} [options] - Login options
 * @param {boolean} [options.session=true] - Save login state in session
 * @param {boolean} [options.keepSessionInfo=false] - Preserve existing session data
 * @param {Function} callback - Callback function (err) => void
 */
req.logIn(user, options, callback);
req.login(user, options, callback); // Alias

/**
 * Log user out of request and session
 * @param {Object} [options] - Logout options
 * @param {boolean} [options.keepSessionInfo=false] - Preserve session data after logout
 * @param {Function} callback - Callback function (err) => void
 */
req.logOut(options, callback);
req.logout(options, callback); // Alias

/**
 * Test if request is authenticated
 * @returns {boolean} True if user is authenticated
 */
req.isAuthenticated();

/**
 * Test if request is unauthenticated
 * @returns {boolean} True if user is not authenticated
 */
req.isUnauthenticated();

Usage Examples:

// Manual login after verification
app.post("/signup", async (req, res, next) => {
  try {
    const user = await User.create(req.body);
    req.logIn(user, (err) => {
      if (err) return next(err);
      res.redirect("/dashboard");
    });
  } catch (err) {
    next(err);
  }
});

// Login without session
app.post("/api/login", (req, res, next) => {
  passport.authenticate("local", (err, user) => {
    if (err) return next(err);
    if (!user) return res.status(401).json({ error: "Invalid credentials" });
    
    req.logIn(user, { session: false }, (err) => {
      if (err) return next(err);
      res.json({ token: generateJWT(user) });
    });
  })(req, res, next);
});

// Logout with session cleanup
app.post("/logout", (req, res, next) => {
  req.logOut((err) => {
    if (err) return next(err);
    res.redirect("/");
  });
});

// Logout preserving some session data
app.post("/logout", (req, res, next) => {
  req.logOut({ keepSessionInfo: true }, (err) => {
    if (err) return next(err);
    res.redirect("/");
  });
});

// Authentication status checking
app.use("/admin", (req, res, next) => {
  if (!req.isAuthenticated()) {
    return res.redirect("/login");
  }
  if (req.user.role !== "admin") {
    return res.status(403).send("Access denied");
  }
  next();
});

// API authentication middleware
app.use("/api", (req, res, next) => {
  if (req.isUnauthenticated()) {
    return res.status(401).json({ error: "Authentication required" });
  }
  next();
});

Request Extension Properties

Internal properties added to HTTP request objects during initialization and authentication processes.

/**
 * Internal session manager reference (added by initialize middleware)
 * Used by request methods to manage login/logout operations
 */
req._sessionManager;

/**
 * Custom user property name (added by initialize middleware)
 * Overrides default 'user' property when userProperty option is specified
 * @type {string}
 */
req._userProperty;

/**
 * Compatibility object for legacy strategy support (added by initialize middleware)
 * @type {Object}
 */
req._passport;

/**
 * Reference to passport instance (in compatibility mode)
 * @type {Authenticator}
 */
req._passport.instance;

/**
 * Current authenticated user object (dynamic property name)
 * Property name defaults to 'user' but can be changed via userProperty option
 * @type {Object|null}
 */
req[req._userProperty || 'user'];

/**
 * Authentication information from strategy (added during authentication)
 * Contains additional data provided by authentication strategy
 * @type {Object|undefined}
 */
req.authInfo;

Usage Examples:

// Initialize with custom user property
app.use(passport.initialize({ userProperty: 'currentUser' }));

// Access user via custom property
app.get('/profile', (req, res) => {
  const user = req.currentUser; // Instead of req.user
  res.json(user);
});

// Access authentication info
app.get('/api/profile',
  passport.authenticate('bearer', { session: false }),
  (req, res) => {
    res.json({
      user: req.user,
      token: req.authInfo.token,    // From bearer strategy
      scope: req.authInfo.scope,    // Additional auth info
      client: req.authInfo.client   // Client information
    });
  }
);

// Check internal properties (for debugging)
app.use((req, res, next) => {
  console.log('Session Manager:', !!req._sessionManager);
  console.log('User Property:', req._userProperty || 'user');
  console.log('Passport Instance:', !!req._passport?.instance);
  next();
});

Session Strategy

Built-in strategy for restoring authentication state from sessions.

/**
 * Session strategy for authentication restoration
 * @param {Object} [options] - Strategy options
 * @param {string} [options.key='passport'] - Session key for storing user data
 * @param {Function} deserializeUser - Function to deserialize user from session
 */
class SessionStrategy {
  constructor(options, deserializeUser);
  name: "session";
  authenticate(req, options);
}

The SessionStrategy is automatically used by Passport and typically doesn't need manual configuration:

// SessionStrategy is used automatically when you call:
app.use(passport.session());

// Which is equivalent to:
app.use(passport.authenticate("session"));

Session Manager

Internal session manager handling login/logout operations with session regeneration and user serialization. Used internally by the Authenticator but can be accessed for advanced use cases.

/**
 * Session manager constructor with flexible parameter handling
 * @param {Object|Function} [options] - Manager options or serializeUser function
 * @param {string} [options.key='passport'] - Session storage key
 * @param {boolean} [options.keepSessionInfo] - Preserve session data during regeneration
 * @param {Function} serializeUser - Function to serialize user into session
 */
function SessionManager(options, serializeUser);
function SessionManager(serializeUser); // When first param is function

/**
 * Log in a user by regenerating session and storing serialized user data
 * @param {Object} req - Express request object with session
 * @param {Object} user - User object to serialize and store
 * @param {Object} [options] - Login options
 * @param {boolean} [options.keepSessionInfo] - Preserve existing session data
 * @param {Function} callback - Callback function (err) => void
 */
SessionManager.prototype.logIn = function(req, user, options, callback);
SessionManager.prototype.logIn = function(req, user, callback); // Options optional

/**
 * Log out user by clearing session data and regenerating session
 * @param {Object} req - Express request object with session
 * @param {Object} [options] - Logout options
 * @param {boolean} [options.keepSessionInfo] - Preserve non-auth session data
 * @param {Function} callback - Callback function (err) => void
 */
SessionManager.prototype.logOut = function(req, options, callback);
SessionManager.prototype.logOut = function(req, callback); // Options optional

Usage Examples:

const { SessionManager } = require("passport/lib/sessionmanager");

// Create custom session manager
const customManager = new SessionManager(
  { key: "auth", keepSessionInfo: true },
  (user, done) => done(null, user.id)
);

// Or with function as first parameter
const simpleManager = new SessionManager((user, done) => {
  done(null, { id: user.id, role: user.role });
});

// Manual login (typically done through req.logIn)
customManager.logIn(req, user, { keepSessionInfo: true }, (err) => {
  if (err) return next(err);
  res.redirect("/dashboard");
});

// Manual logout (typically done through req.logOut)
customManager.logOut(req, (err) => {
  if (err) return next(err);
  res.redirect("/");
});

Authentication Info Transformation

Transform authentication info before it's attached to the request.

/**
 * Register function to transform auth info with support for multiple signatures
 * @param {Function} fn - Transform function with flexible arity:
 *   - (info) => transformedInfo (synchronous, arity 1)
 *   - (info, done) => void (asynchronous, arity 2)
 *   - (req, info, done) => void (with request access, arity 3)
 */
passport.transformAuthInfo(fn);

Usage Examples:

// Synchronous transformation (arity 1)
passport.transformAuthInfo((info) => {
  return {
    ...info,
    timestamp: new Date().toISOString(),
    source: "api"
  };
});

// Asynchronous transformation (arity 2)
passport.transformAuthInfo((info, done) => {
  if (info.clientID) {
    Client.findById(info.clientID, (err, client) => {
      if (err) return done(err);
      info.client = client;
      done(null, info);
    });
  } else {
    done(null, info);
  }
});

// Transformation with request access (arity 3)
passport.transformAuthInfo((req, info, done) => {
  const enhanced = {
    ...info,
    userAgent: req.get("User-Agent"),
    ip: req.ip,
    timestamp: new Date().toISOString()
  };
  
  // Access user session data if available
  if (req.session && req.session.preferences) {
    enhanced.preferences = req.session.preferences;
  }
  
  done(null, enhanced);
});

// Use in route handler
app.get("/api/profile",
  passport.authenticate("bearer", { session: false }),
  (req, res) => {
    res.json({
      user: req.user,
      authInfo: req.authInfo, // Contains transformed info
      client: req.authInfo.client, // Added by transformer
      timestamp: req.authInfo.timestamp // Added by transformer
    });
  }
);

Session Data Structure

Session data is stored at req.session[key] where key defaults to 'passport':

// Session structure
req.session.passport = {
  user: "<serialized user data>"
};

// Example with user ID
req.session.passport = {
  user: "507f1f77bcf86cd799439011"
};

// Example with user object
req.session.passport = {
  user: {
    id: "507f1f77bcf86cd799439011",
    role: "admin",
    lastLogin: "2023-10-15T10:30:00Z"
  }
};

Common Session Patterns

Complete Session Setup

const express = require("express");
const session = require("express-session");
const MongoStore = require("connect-mongo");
const passport = require("passport");

const app = express();

// Session configuration
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  store: MongoStore.create({
    mongoUrl: process.env.MONGODB_URI
  }),
  cookie: {
    secure: process.env.NODE_ENV === "production",
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
  }
}));

// Passport configuration
app.use(passport.initialize());
app.use(passport.session());

// User serialization
passport.serializeUser((user, done) => {
  done(null, user._id);
});

passport.deserializeUser(async (id, done) => {
  try {
    const user = await User.findById(id);
    done(null, user);
  } catch (err) {
    done(err);
  }
});

Session-based Authentication Flow

// Login route
app.post("/login",
  passport.authenticate("local", {
    successRedirect: "/dashboard",
    failureRedirect: "/login",
    failureFlash: true
  })
);

// Protected route
app.get("/dashboard", (req, res) => {
  if (!req.isAuthenticated()) {
    return res.redirect("/login");
  }
  res.render("dashboard", { user: req.user });
});

// Logout route
app.post("/logout", (req, res, next) => {
  req.logOut((err) => {
    if (err) return next(err);
    res.redirect("/");
  });
});

API with Mixed Authentication

// Session-based web routes
app.get("/profile", 
  passport.authenticate("session"),
  (req, res) => {
    res.render("profile", { user: req.user });
  }
);

// Token-based API routes
app.get("/api/profile",
  passport.authenticate("bearer", { session: false }),
  (req, res) => {
    res.json(req.user);
  }
);

// Flexible API endpoint
app.get("/api/user",
  passport.authenticate(["session", "bearer"], { session: false }),
  (req, res) => {
    res.json({
      user: req.user,
      authMethod: req.authInfo ? "token" : "session"
    });
  }
);

Install with Tessl CLI

npx tessl i tessl/npm-passport

docs

core-authentication.md

index.md

session-management.md

strategy-development.md

tile.json