Simple, unobtrusive authentication for Node.js
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
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);
}
});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();
});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();
});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"));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 optionalUsage 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("/");
});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 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"
}
};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);
}
});// 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("/");
});
});// 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