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 0000000000..3a3ccce4a1 Binary files /dev/null and b/examples/passport-jwt-example/assets/passport_example.gif differ diff --git a/examples/passport-jwt-example/cjs/index.html b/examples/passport-jwt-example/cjs/index.html new file mode 100644 index 0000000000..04936a55da --- /dev/null +++ b/examples/passport-jwt-example/cjs/index.html @@ -0,0 +1,154 @@ + + + + + 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 + } +}