docs: update the Passport.js example

This commit is contained in:
Damien Arrachequesne
2024-01-12 17:08:19 +01:00
parent 6ab2509d52
commit d943c3e0b0
17 changed files with 641 additions and 156 deletions

View File

@@ -5,6 +5,8 @@ This example shows how to retrieve the authentication context from a basic [Expr
![Passport example](assets/passport_example.gif)
Please read the related guide: https://socket.io/how-to/use-with-passport
## How to use
```

View File

@@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Passport example</title>
</head>
<body>
<p>Authenticated!</p>
<table>
<tbody>
<tr>
<td>Status</td>
<td><span id="status">Disconnected</span></td>
</tr>
<tr>
<td>Socket ID</td>
<td><span id="socket-id"></span></td>
</tr>
<tr>
<td>Username</td>
<td><span id="username"></span></td>
</tr>
</tbody>
</table>
<form action="/logout" method="post">
<div>
<input type="submit" value="Log out" />
</div>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const socketIdSpan = document.getElementById('socket-id');
const usernameSpan = document.getElementById('username');
const statusSpan = document.getElementById('status');
socket.on('connect', () => {
statusSpan.innerText = 'connected';
socketIdSpan.innerText = socket.id;
socket.emit('whoami', (username) => {
usernameSpan.innerText = username;
});
});
socket.on('disconnect', () => {
statusSpan.innerText = 'disconnected';
socketIdSpan.innerText = '-';
});
</script>
</body>
</html>

View File

@@ -0,0 +1,109 @@
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.initialize());
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: "/",
}),
);
app.post("/logout", (req, res) => {
const sessionId = req.session.id;
req.session.destroy(() => {
// disconnect all Socket.IO connections linked to this session ID
io.to(`session:${sessionId}`).disconnectSockets();
res.status(204).end();
});
});
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);
io.engine.use(sessionMiddleware);
io.engine.use(passport.initialize());
io.engine.use(passport.session());
io.engine.use(
(req, res, next) => {
if (req.user) {
next();
} else {
res.writeHead(401);
res.end();
}
},
);
io.on("connection", (socket) => {
const req = socket.request;
socket.join(`session:${req.session.id}`);
socket.join(`user:${req.user.id}`);
socket.on("whoami", (cb) => {
cb(req.user.username);
});
});
httpServer.listen(port, () => {
console.log(`application is running at: http://localhost:${port}`);
});

View File

@@ -8,17 +8,17 @@
<p>Not authenticated</p>
<form action="/login" method="post">
<div>
<label>Username:</label>
<input type="text" name="username" />
<label for="username">Username:</label>
<input type="text" id="username" name="username" />
<br/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password" />
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
</body>
</html>
</html>

View File

@@ -0,0 +1,20 @@
{
"name": "passport-example",
"version": "0.0.1",
"private": true,
"type": "commonjs",
"description": "Example with passport (https://www.passportjs.org)",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "~4.17.3",
"express-session": "~1.17.2",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"socket.io": "^4.7.2"
},
"devDependencies": {
"prettier": "^3.1.1"
}
}

View File

@@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Passport example</title>
</head>
<body>
<p>Authenticated!</p>
<table>
<tbody>
<tr>
<td>Status</td>
<td><span id="status">Disconnected</span></td>
</tr>
<tr>
<td>Socket ID</td>
<td><span id="socket-id"></span></td>
</tr>
<tr>
<td>Username</td>
<td><span id="username"></span></td>
</tr>
</tbody>
</table>
<form action="/logout" method="post">
<div>
<input type="submit" value="Log out" />
</div>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const socketIdSpan = document.getElementById('socket-id');
const usernameSpan = document.getElementById('username');
const statusSpan = document.getElementById('status');
socket.on('connect', () => {
statusSpan.innerText = 'connected';
socketIdSpan.innerText = socket.id;
socket.emit('whoami', (username) => {
usernameSpan.innerText = username;
});
});
socket.on('disconnect', () => {
statusSpan.innerText = 'disconnected';
socketIdSpan.innerText = '-';
});
</script>
</body>
</html>

View File

@@ -0,0 +1,112 @@
import express from "express";
import { createServer } from "http";
import { Server } from "socket.io";
import session from "express-session";
import bodyParser from "body-parser";
import passport from "passport";
import { Strategy as LocalStrategy } from "passport-local";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
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.initialize());
app.use(passport.session());
const __dirname = dirname(fileURLToPath(import.meta.url));
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: "/",
}),
);
app.post("/logout", (req, res) => {
const sessionId = req.session.id;
req.session.destroy(() => {
// disconnect all Socket.IO connections linked to this session ID
io.to(`session:${sessionId}`).disconnectSockets();
res.status(204).end();
});
});
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);
io.engine.use(sessionMiddleware);
io.engine.use(passport.initialize());
io.engine.use(passport.session());
io.engine.use(
(req, res, next) => {
if (req.user) {
next();
} else {
res.writeHead(401);
res.end();
}
},
);
io.on("connection", (socket) => {
const req = socket.request;
socket.join(`session:${req.session.id}`);
socket.join(`user:${req.user.id}`);
socket.on("whoami", (cb) => {
cb(req.user.username);
});
});
httpServer.listen(port, () => {
console.log(`application is running at: http://localhost:${port}`);
});

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Passport example</title>
</head>
<body>
<p>Not authenticated</p>
<form action="/login" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" />
<br/>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
</body>
</html>

View File

@@ -0,0 +1,20 @@
{
"name": "passport-example",
"version": "0.0.1",
"private": true,
"type": "module",
"description": "Example with passport (https://www.passportjs.org)",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "~4.17.3",
"express-session": "~1.17.2",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"socket.io": "^4.7.2"
},
"devDependencies": {
"prettier": "^3.1.1"
}
}

View File

@@ -1,31 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Passport example</title>
</head>
<body>
<p>Authenticated!</p>
<p>Socket ID: <span id="socketId"></span></p>
<p>Username: <span id="username"></span></p>
<form action="/logout" method="post">
<div>
<input type="submit" value="Log out" />
</div>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const socketIdSpan = document.getElementById("socketId");
const usernameSpan = document.getElementById("username");
socket.on('connect', () => {
socketIdSpan.innerText = socket.id;
socket.emit('whoami', (username) => {
usernameSpan.innerText = username;
});
});
</script>
</body>
</html>

View File

@@ -1,104 +0,0 @@
const app = require("express")();
const server = require("http").createServer(app);
const port = process.env.PORT || 3000;
const session = require("express-session");
const bodyParser = require("body-parser");
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const sessionMiddleware = session({ secret: "changeit", resave: false, saveUninitialized: false });
app.use(sessionMiddleware);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.initialize());
app.use(passport.session());
const DUMMY_USER = {
id: 1,
username: "john",
};
passport.use(
new LocalStrategy((username, password, done) => {
if (username === "john" && password === "doe") {
console.log("authentication OK");
return done(null, DUMMY_USER);
} else {
console.log("wrong credentials");
return done(null, false);
}
})
);
app.get("/", (req, res) => {
const isAuthenticated = !!req.user;
if (isAuthenticated) {
console.log(`user is authenticated, session is ${req.session.id}`);
} else {
console.log("unknown user");
}
res.sendFile(isAuthenticated ? "index.html" : "login.html", { root: __dirname });
});
app.post(
"/login",
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/",
})
);
app.post("/logout", (req, res) => {
console.log(`logout ${req.session.id}`);
const socketId = req.session.socketId;
if (socketId && io.of("/").sockets.get(socketId)) {
console.log(`forcefully closing socket ${socketId}`);
io.of("/").sockets.get(socketId).disconnect(true);
}
req.logout();
res.cookie("connect.sid", "", { expires: new Date() });
res.redirect("/");
});
passport.serializeUser((user, cb) => {
console.log(`serializeUser ${user.id}`);
cb(null, user.id);
});
passport.deserializeUser((id, cb) => {
console.log(`deserializeUser ${id}`);
cb(null, DUMMY_USER);
});
const io = require('socket.io')(server);
// convert a connect middleware to a Socket.IO middleware
const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
io.use(wrap(sessionMiddleware));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use((socket, next) => {
if (socket.request.user) {
next();
} else {
next(new Error('unauthorized'))
}
});
io.on('connect', (socket) => {
console.log(`new connection ${socket.id}`);
socket.on('whoami', (cb) => {
cb(socket.request.user ? socket.request.user.username : '');
});
const session = socket.request.session;
console.log(`saving sid ${socket.id} in session ${session.id}`);
session.socketId = socket.id;
session.save();
});
server.listen(port, () => {
console.log(`application is running at: http://localhost:${port}`);
});

View File

@@ -1,16 +0,0 @@
{
"name": "passport-example",
"version": "0.0.1",
"description": "Example with Passport (http://www.passportjs.org/)",
"dependencies": {
"body-parser": "~1.19.0",
"express": "~4.17.1",
"express-session": "~1.17.1",
"passport": "~0.4.1",
"passport-local": "~1.0.0",
"socket.io": "^4.0.0"
},
"scripts": {
"start": "node index.js"
}
}

View File

@@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Passport example</title>
</head>
<body>
<p>Authenticated!</p>
<table>
<tbody>
<tr>
<td>Status</td>
<td><span id="status">Disconnected</span></td>
</tr>
<tr>
<td>Socket ID</td>
<td><span id="socket-id"></span></td>
</tr>
<tr>
<td>Username</td>
<td><span id="username"></span></td>
</tr>
</tbody>
</table>
<form action="/logout" method="post">
<div>
<input type="submit" value="Log out" />
</div>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const socketIdSpan = document.getElementById('socket-id');
const usernameSpan = document.getElementById('username');
const statusSpan = document.getElementById('status');
socket.on('connect', () => {
statusSpan.innerText = 'connected';
socketIdSpan.innerText = socket.id;
socket.emit('whoami', (username) => {
usernameSpan.innerText = username;
});
});
socket.on('disconnect', () => {
statusSpan.innerText = 'disconnected';
socketIdSpan.innerText = '-';
});
</script>
</body>
</html>

View File

@@ -0,0 +1,122 @@
import express = require("express");
import { createServer, ServerResponse } from "http";
import { Server } from "socket.io";
import session from "express-session";
import { type Request } from "express";
import bodyParser = require("body-parser");
import passport = require("passport");
import { Strategy as LocalStrategy } from "passport-local";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
declare global {
namespace Express {
interface User {
id: number;
username: string;
}
}
}
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.initialize());
app.use(passport.session());
const __dirname = dirname(fileURLToPath(import.meta.url));
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: "/",
}),
);
app.post("/logout", (req, res) => {
const sessionId = req.session.id;
req.session.destroy(() => {
// disconnect all Socket.IO connections linked to this session ID
io.to(`session:${sessionId}`).disconnectSockets();
res.status(204).end();
});
});
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: Express.User, cb) => {
console.log(`deserializeUser ${user.id}`);
cb(null, user);
});
const io = new Server(httpServer);
io.engine.use(sessionMiddleware);
io.engine.use(passport.initialize());
io.engine.use(passport.session());
io.engine.use(
(req: { user: Express.User }, res: ServerResponse, next: Function) => {
if (req.user) {
next();
} else {
res.writeHead(401);
res.end();
}
},
);
io.on("connection", (socket) => {
const req = socket.request as Request & { user: Express.User };
socket.join(`session:${req.session.id}`);
socket.join(`user:${req.user.id}`);
socket.on("whoami", (cb) => {
cb(req.user.username);
});
});
httpServer.listen(port, () => {
console.log(`application is running at: http://localhost:${port}`);
});

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Passport example</title>
</head>
<body>
<p>Not authenticated</p>
<form action="/login" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" />
<br/>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
</body>
</html>

View File

@@ -0,0 +1,27 @@
{
"name": "passport-example",
"version": "0.0.1",
"private": true,
"type": "module",
"description": "Example with passport (https://www.passportjs.org)",
"scripts": {
"start": "ts-node index.ts"
},
"dependencies": {
"@types/express": "^4.17.17",
"@types/express-session": "^1.17.7",
"@types/node": "^20.6.0",
"@types/passport": "^1.0.16",
"express": "~4.17.3",
"express-session": "~1.17.2",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"socket.io": "^4.7.2",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
},
"devDependencies": {
"@types/passport-local": "^1.0.38",
"prettier": "^3.1.1"
}
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022",
"strict": true
},
"ts-node": {
"esm": true
}
}