Skip to main content

How to use with Passport.js

Let's start from a basic application:

const express = require("express");
const { createServer } = require("node:http");
const { Server } = require("socket.io");
const session = require("express-session");
const bodyParser = require("body-parser");
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const { join } = require("node:path");

const port = process.env.PORT || 3000;

const app = express();
const httpServer = createServer(app);

const sessionMiddleware = session({
secret: "changeit",
resave: true,
saveUninitialized: true,
});

app.use(sessionMiddleware);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.session());

app.get("/", (req, res) => {
if (!req.user) {
return res.redirect("/login");
}
res.sendFile(join(__dirname, "index.html"));
});

app.get("/login", (req, res) => {
if (req.user) {
return res.redirect("/");
}
res.sendFile(join(__dirname, "login.html"));
});

app.post(
"/login",
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/",
}),
);

passport.use(
new LocalStrategy((username, password, done) => {
if (username === "john" && password === "changeit") {
console.log("authentication OK");
return done(null, { id: 1, username });
} else {
console.log("wrong credentials");
return done(null, false);
}
}),
);

passport.serializeUser((user, cb) => {
console.log(`serializeUser ${user.id}`);
cb(null, user);
});

passport.deserializeUser((user, cb) => {
console.log(`deserializeUser ${user.id}`);
cb(null, user);
});

const io = new Server(httpServer);

httpServer.listen(port, () => {
console.log(`application is running at: http://localhost:${port}`);
});

Sharing the user context

The user context can be shared with the Socket.IO server by calling:

function onlyForHandshake(middleware) {
return (req, res, next) => {
const isHandshake = req._query.sid === undefined;
if (isHandshake) {
middleware(req, res, next);
} else {
next();
}
};
}

io.engine.use(onlyForHandshake(sessionMiddleware));
io.engine.use(onlyForHandshake(passport.session()));
io.engine.use(
onlyForHandshake((req, res, next) => {
if (req.user) {
next();
} else {
res.writeHead(401);
res.end();
}
}),
);

Here's what happens:

  • the express-session middleware retrieves the session context from the cookie
  • the passport middleware extracts the user context from the session
  • and finally, the handshake is validated if the user context was found
tip

The onlyForHandshake() method ensures that the middlewares are only applied to the first HTTP request of the session.

You'll now have access to the user object:

io.on("connection", (socket) => {
const user = socket.request.user;
});

Using the user ID

You can use the user ID to make the link between Express and Socket.IO:

io.on("connection", (socket) => {
const userId = socket.request.user.id;

// the user ID is used as a room
socket.join(`user:${userId}`);
});

Which allows you to easily broadcast an event to all the connections of a given user:

io.to(`user:${userId}`).emit("foo", "bar");

You can also check whether a user is currently connected:

const sockets = await io.in(`user:${userId}`).fetchSockets();
const isUserConnected = sockets.length > 0;

That's it for the compatibility with Passport.js. Thanks for reading!

The complete example can be found here.

tip

You can run this example directly in your browser on: