From 914a8bd2b97f559a666ede08df8610cfe05e9cda Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Sat, 13 Jan 2024 17:13:20 +0100 Subject: [PATCH] docs: add example with JWT Related: https://github.com/socketio/socket.io/issues/4910 --- examples/passport-jwt-example/README.md | 41 +++++ .../assets/passport_example.gif | Bin 0 -> 34008 bytes examples/passport-jwt-example/cjs/index.html | 154 ++++++++++++++++++ examples/passport-jwt-example/cjs/index.js | 100 ++++++++++++ .../passport-jwt-example/cjs/package.json | 21 +++ examples/passport-jwt-example/esm/index.html | 154 ++++++++++++++++++ examples/passport-jwt-example/esm/index.js | 101 ++++++++++++ .../passport-jwt-example/esm/package.json | 21 +++ examples/passport-jwt-example/ts/index.html | 154 ++++++++++++++++++ examples/passport-jwt-example/ts/index.ts | 113 +++++++++++++ examples/passport-jwt-example/ts/package.json | 27 +++ .../passport-jwt-example/ts/tsconfig.json | 11 ++ 12 files changed, 897 insertions(+) create mode 100644 examples/passport-jwt-example/README.md create mode 100644 examples/passport-jwt-example/assets/passport_example.gif create mode 100644 examples/passport-jwt-example/cjs/index.html create mode 100644 examples/passport-jwt-example/cjs/index.js create mode 100644 examples/passport-jwt-example/cjs/package.json create mode 100644 examples/passport-jwt-example/esm/index.html create mode 100644 examples/passport-jwt-example/esm/index.js create mode 100644 examples/passport-jwt-example/esm/package.json create mode 100644 examples/passport-jwt-example/ts/index.html create mode 100644 examples/passport-jwt-example/ts/index.ts create mode 100644 examples/passport-jwt-example/ts/package.json create mode 100644 examples/passport-jwt-example/ts/tsconfig.json diff --git a/examples/passport-jwt-example/README.md b/examples/passport-jwt-example/README.md new file mode 100644 index 0000000000..cf675b0d49 --- /dev/null +++ b/examples/passport-jwt-example/README.md @@ -0,0 +1,41 @@ + +# Example with [`passport-jwt`](https://www.passportjs.org/packages/passport-jwt/) + +This example shows how to retrieve the authentication context from a basic [Express](http://expressjs.com/) + [Passport](http://www.passportjs.org/) application. + +![Passport example](assets/passport_example.gif) + +Please read the related guide: https://socket.io/how-to/use-with-jwt + +## How to use + +``` +$ npm ci && npm start +``` + +And point your browser to `http://localhost:3000`. Optionally, specify a port by supplying the `PORT` env variable. + +## How it works + +The client sends the JWT in the headers: + +```js +const socket = io({ + extraHeaders: { + authorization: `bearer token` + } +}); +``` + +And the Socket.IO server then parses the token and retrieves the user context: + +```js +io.engine.use((req, res, next) => { + const isHandshake = req._query.sid === undefined; + if (isHandshake) { + passport.authenticate("jwt", { session: false })(req, res, next); + } else { + next(); + } +}); +``` diff --git a/examples/passport-jwt-example/assets/passport_example.gif b/examples/passport-jwt-example/assets/passport_example.gif new file mode 100644 index 0000000000000000000000000000000000000000..3a3ccce4a129b82ff566f7e58811cff41b2e37d8 GIT binary patch literal 34008 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 + + + + Passport JWT example + + + + + + + + + + diff --git a/examples/passport-jwt-example/cjs/index.js b/examples/passport-jwt-example/cjs/index.js new file mode 100644 index 0000000000..00e241cf7c --- /dev/null +++ b/examples/passport-jwt-example/cjs/index.js @@ -0,0 +1,100 @@ +const express = require("express"); +const { createServer } = require("node:http"); +const { join } = require("node:path"); +const passport = require("passport"); +const passportJwt = require("passport-jwt"); +const JwtStrategy = passportJwt.Strategy; +const ExtractJwt = passportJwt.ExtractJwt; +const bodyParser = require("body-parser"); +const { Server } = require("socket.io"); +const jwt = require("jsonwebtoken"); + +const port = process.env.PORT || 3000; +const jwtSecret = "Mys3cr3t"; + +const app = express(); +const httpServer = createServer(app); + +app.use(bodyParser.json()); + +app.get("/", (req, res) => { + res.sendFile(join(__dirname, "index.html")); +}); + +app.get( + "/self", + passport.authenticate("jwt", { session: false }), + (req, res) => { + if (req.user) { + res.send(req.user); + } else { + res.status(401).end(); + } + }, +); + +app.post("/login", (req, res) => { + if (req.body.username === "john" && req.body.password === "changeit") { + console.log("authentication OK"); + + const user = { + id: 1, + username: "john", + }; + + const token = jwt.sign( + { + data: user, + }, + jwtSecret, + { + issuer: "accounts.examplesoft.com", + audience: "yoursite.net", + expiresIn: "1h", + }, + ); + + res.json({ token }); + } else { + console.log("wrong credentials"); + res.status(401).end(); + } +}); + +const jwtDecodeOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: jwtSecret, + issuer: "accounts.examplesoft.com", + audience: "yoursite.net", +}; + +passport.use( + new JwtStrategy(jwtDecodeOptions, (payload, done) => { + return done(null, payload.data); + }), +); + +const io = new Server(httpServer); + +io.engine.use((req, res, next) => { + const isHandshake = req._query.sid === undefined; + if (isHandshake) { + passport.authenticate("jwt", { session: false })(req, res, next); + } else { + next(); + } +}); + +io.on("connection", (socket) => { + const req = socket.request; + + 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}`); +}); diff --git a/examples/passport-jwt-example/cjs/package.json b/examples/passport-jwt-example/cjs/package.json new file mode 100644 index 0000000000..616728bd4e --- /dev/null +++ b/examples/passport-jwt-example/cjs/package.json @@ -0,0 +1,21 @@ +{ + "name": "passport-jwt-example", + "version": "0.0.1", + "private": true, + "type": "commonjs", + "description": "Example with passport and JWT (https://www.passportjs.org/packages/passport-jwt/)", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "body-parser": "^1.20.2", + "express": "~4.17.3", + "jsonwebtoken": "^9.0.2", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "socket.io": "^4.7.2" + }, + "devDependencies": { + "prettier": "^3.1.1" + } +} diff --git a/examples/passport-jwt-example/esm/index.html b/examples/passport-jwt-example/esm/index.html new file mode 100644 index 0000000000..04936a55da --- /dev/null +++ b/examples/passport-jwt-example/esm/index.html @@ -0,0 +1,154 @@ + + + + + Passport JWT example + + + + + + + + + + diff --git a/examples/passport-jwt-example/esm/index.js b/examples/passport-jwt-example/esm/index.js new file mode 100644 index 0000000000..d2d9f37977 --- /dev/null +++ b/examples/passport-jwt-example/esm/index.js @@ -0,0 +1,101 @@ +import express from "express"; +import { createServer } from "node:http"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import passport from "passport"; +import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt"; +import bodyParser from "body-parser"; +import { Server } from "socket.io"; +import jwt from "jsonwebtoken"; + +const port = process.env.PORT || 3000; +const jwtSecret = "Mys3cr3t"; + +const app = express(); +const httpServer = createServer(app); + +app.use(bodyParser.json()); + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +app.get("/", (req, res) => { + res.sendFile(join(__dirname, "index.html")); +}); + +app.get( + "/self", + passport.authenticate("jwt", { session: false }), + (req, res) => { + if (req.user) { + res.send(req.user); + } else { + res.status(401).end(); + } + }, +); + +app.post("/login", (req, res) => { + if (req.body.username === "john" && req.body.password === "changeit") { + console.log("authentication OK"); + + const user = { + id: 1, + username: "john", + }; + + const token = jwt.sign( + { + data: user, + }, + jwtSecret, + { + issuer: "accounts.examplesoft.com", + audience: "yoursite.net", + expiresIn: "1h", + }, + ); + + res.json({ token }); + } else { + console.log("wrong credentials"); + res.status(401).end(); + } +}); + +const jwtDecodeOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: jwtSecret, + issuer: "accounts.examplesoft.com", + audience: "yoursite.net", +}; + +passport.use( + new JwtStrategy(jwtDecodeOptions, (payload, done) => { + return done(null, payload.data); + }), +); + +const io = new Server(httpServer); + +io.engine.use((req, res, next) => { + const isHandshake = req._query.sid === undefined; + if (isHandshake) { + passport.authenticate("jwt", { session: false })(req, res, next); + } else { + next(); + } +}); + +io.on("connection", (socket) => { + const req = socket.request; + + 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}`); +}); diff --git a/examples/passport-jwt-example/esm/package.json b/examples/passport-jwt-example/esm/package.json new file mode 100644 index 0000000000..1e2d7e7b9d --- /dev/null +++ b/examples/passport-jwt-example/esm/package.json @@ -0,0 +1,21 @@ +{ + "name": "passport-jwt-example", + "version": "0.0.1", + "private": true, + "type": "module", + "description": "Example with passport and JWT (https://www.passportjs.org/packages/passport-jwt/)", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "body-parser": "^1.20.2", + "express": "~4.17.3", + "jsonwebtoken": "^9.0.2", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "socket.io": "^4.7.2" + }, + "devDependencies": { + "prettier": "^3.1.1" + } +} diff --git a/examples/passport-jwt-example/ts/index.html b/examples/passport-jwt-example/ts/index.html new file mode 100644 index 0000000000..04936a55da --- /dev/null +++ b/examples/passport-jwt-example/ts/index.html @@ -0,0 +1,154 @@ + + + + + Passport JWT example + + + + + + + + + + diff --git a/examples/passport-jwt-example/ts/index.ts b/examples/passport-jwt-example/ts/index.ts new file mode 100644 index 0000000000..a0ef4d74ed --- /dev/null +++ b/examples/passport-jwt-example/ts/index.ts @@ -0,0 +1,113 @@ +import express from "express"; +import { type Request, type Response } from "express"; +import { createServer } from "node:http"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import passport from "passport"; +import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt"; +import bodyParser from "body-parser"; +import { Server } from "socket.io"; +import jwt from "jsonwebtoken"; + +declare global { + namespace Express { + interface User { + id: number; + username: string; + } + } +} + +const port = process.env.PORT || 3000; +const jwtSecret = "Mys3cr3t"; + +const app = express(); +const httpServer = createServer(app); + +app.use(bodyParser.json()); + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +app.get("/", (req, res) => { + res.sendFile(join(__dirname, "index.html")); +}); + +app.get( + "/self", + passport.authenticate("jwt", { session: false }), + (req, res) => { + if (req.user) { + res.send(req.user); + } else { + res.status(401).end(); + } + }, +); + +app.post("/login", (req, res) => { + if (req.body.username === "john" && req.body.password === "changeit") { + console.log("authentication OK"); + + const user = { + id: 1, + username: "john", + }; + + const token = jwt.sign( + { + data: user, + }, + jwtSecret, + { + issuer: "accounts.examplesoft.com", + audience: "yoursite.net", + expiresIn: "1h", + }, + ); + + res.json({ token }); + } else { + console.log("wrong credentials"); + res.status(401).end(); + } +}); + +const jwtDecodeOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: jwtSecret, + issuer: "accounts.examplesoft.com", + audience: "yoursite.net", +}; + +passport.use( + new JwtStrategy(jwtDecodeOptions, (payload, done) => { + return done(null, payload.data); + }), +); + +const io = new Server(httpServer); + +io.engine.use( + (req: { _query: Record }, res: Response, next: Function) => { + const isHandshake = req._query.sid === undefined; + if (isHandshake) { + passport.authenticate("jwt", { session: false })(req, res, next); + } else { + next(); + } + }, +); + +io.on("connection", (socket) => { + const req = socket.request as Request & { user: Express.User }; + + 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}`); +}); diff --git a/examples/passport-jwt-example/ts/package.json b/examples/passport-jwt-example/ts/package.json new file mode 100644 index 0000000000..f53cea59c4 --- /dev/null +++ b/examples/passport-jwt-example/ts/package.json @@ -0,0 +1,27 @@ +{ + "name": "passport-jwt-example", + "version": "0.0.1", + "private": true, + "type": "module", + "description": "Example with passport and JWT (https://www.passportjs.org/packages/passport-jwt/)", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.5", + "@types/passport": "^1.0.16", + "@types/passport-jwt": "^4.0.0", + "body-parser": "^1.20.2", + "express": "~4.17.3", + "jsonwebtoken": "^9.0.2", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "socket.io": "^4.7.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "devDependencies": { + "prettier": "^3.1.1" + } +} diff --git a/examples/passport-jwt-example/ts/tsconfig.json b/examples/passport-jwt-example/ts/tsconfig.json new file mode 100644 index 0000000000..fe03ed87a1 --- /dev/null +++ b/examples/passport-jwt-example/ts/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "ES2022", + "strict": true + }, + "ts-node": { + "esm": true + } +}