Skip to content

Commit 914a8bd

Browse files
committedJan 13, 2024
docs: add example with JWT
Related: #4910
1 parent d943c3e commit 914a8bd

File tree

12 files changed

+897
-0
lines changed

12 files changed

+897
-0
lines changed
 
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
# Example with [`passport-jwt`](https://www.passportjs.org/packages/passport-jwt/)
3+
4+
This example shows how to retrieve the authentication context from a basic [Express](http://expressjs.com/) + [Passport](http://www.passportjs.org/) application.
5+
6+
![Passport example](assets/passport_example.gif)
7+
8+
Please read the related guide: https://socket.io/how-to/use-with-jwt
9+
10+
## How to use
11+
12+
```
13+
$ npm ci && npm start
14+
```
15+
16+
And point your browser to `http://localhost:3000`. Optionally, specify a port by supplying the `PORT` env variable.
17+
18+
## How it works
19+
20+
The client sends the JWT in the headers:
21+
22+
```js
23+
const socket = io({
24+
extraHeaders: {
25+
authorization: `bearer token`
26+
}
27+
});
28+
```
29+
30+
And the Socket.IO server then parses the token and retrieves the user context:
31+
32+
```js
33+
io.engine.use((req, res, next) => {
34+
const isHandshake = req._query.sid === undefined;
35+
if (isHandshake) {
36+
passport.authenticate("jwt", { session: false })(req, res, next);
37+
} else {
38+
next();
39+
}
40+
});
41+
```
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Passport JWT example</title>
6+
</head>
7+
<body>
8+
<div id="login-panel" style="display: none">
9+
<p>Not authenticated</p>
10+
<form id="login-form">
11+
<div>
12+
<label for="username">Username:</label>
13+
<input type="text" id="username" name="username" value="john" />
14+
<br/>
15+
</div>
16+
<div>
17+
<label for="password">Password:</label>
18+
<input type="password" id="password" name="password" value="changeit" />
19+
</div>
20+
<div>
21+
<input type="submit" value="Submit" />
22+
</div>
23+
</form>
24+
</div>
25+
26+
<div id="home-panel" style="display: none">
27+
<p>Authenticated!</p>
28+
29+
<table>
30+
<tbody>
31+
<tr>
32+
<td>Status</td>
33+
<td><span id="status">Disconnected</span></td>
34+
</tr>
35+
<tr>
36+
<td>Socket ID</td>
37+
<td><span id="socket-id"></span></td>
38+
</tr>
39+
<tr>
40+
<td>Username</td>
41+
<td><span id="name"></span></td>
42+
</tr>
43+
</tbody>
44+
</table>
45+
46+
<form id="logout-form">
47+
<div>
48+
<input type="submit" value="Log out" />
49+
</div>
50+
</form>
51+
</div>
52+
53+
<script src="/socket.io/socket.io.js"></script>
54+
<script>
55+
const loginPanel = document.getElementById('login-panel');
56+
const homePanel = document.getElementById('home-panel');
57+
const loginForm = document.getElementById('login-form');
58+
const usernameInput = document.getElementById('username');
59+
const passwordInput = document.getElementById('password');
60+
const statusSpan = document.getElementById('status');
61+
const socketIdSpan = document.getElementById('socket-id');
62+
const usernameSpan = document.getElementById('name');
63+
const logoutForm = document.getElementById('logout-form');
64+
65+
let socket;
66+
67+
async function main() {
68+
const token = localStorage.getItem('token');
69+
70+
if (!token) {
71+
return showLoginPanel();
72+
}
73+
74+
const res = await fetch('/self', {
75+
headers: {
76+
authorization: `bearer ${token}`
77+
}
78+
});
79+
80+
if (res.status === 200) {
81+
showHomePanel();
82+
} else {
83+
showLoginPanel();
84+
}
85+
}
86+
87+
function showHomePanel() {
88+
loginPanel.style.display = 'none';
89+
homePanel.style.display = 'block';
90+
91+
// this will only work if HTTP long-polling is enabled, since WebSockets do not support providing additional headers
92+
socket = io({
93+
extraHeaders: {
94+
authorization: `bearer ${localStorage.getItem('token')}`
95+
}
96+
});
97+
98+
socket.on('connect', () => {
99+
statusSpan.innerText = 'connected';
100+
socketIdSpan.innerText = socket.id;
101+
102+
socket.emit('whoami', (username) => {
103+
usernameSpan.innerText = username;
104+
});
105+
});
106+
107+
socket.on('disconnect', () => {
108+
statusSpan.innerText = 'disconnected';
109+
socketIdSpan.innerText = '-';
110+
});
111+
}
112+
113+
function showLoginPanel() {
114+
loginPanel.style.display = 'block';
115+
homePanel.style.display = 'none';
116+
}
117+
118+
loginForm.onsubmit = async function (e) {
119+
e.preventDefault();
120+
121+
const res = await fetch('/#', {
122+
method: 'post',
123+
headers: {
124+
'content-type': 'application/json'
125+
},
126+
body: JSON.stringify({
127+
username: usernameInput.value,
128+
password: passwordInput.value
129+
})
130+
})
131+
132+
if (res.status === 200) {
133+
const { token } = await res.json();
134+
localStorage.setItem('token', token);
135+
136+
showHomePanel();
137+
} else {
138+
passwordInput.value = '';
139+
}
140+
}
141+
142+
logoutForm.onsubmit = function (e) {
143+
e.preventDefault();
144+
145+
socket.disconnect();
146+
localStorage.removeItem('token');
147+
148+
showLoginPanel();
149+
}
150+
151+
main();
152+
</script>
153+
</body>
154+
</html>
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const express = require("express");
2+
const { createServer } = require("node:http");
3+
const { join } = require("node:path");
4+
const passport = require("passport");
5+
const passportJwt = require("passport-jwt");
6+
const JwtStrategy = passportJwt.Strategy;
7+
const ExtractJwt = passportJwt.ExtractJwt;
8+
const bodyParser = require("body-parser");
9+
const { Server } = require("socket.io");
10+
const jwt = require("jsonwebtoken");
11+
12+
const port = process.env.PORT || 3000;
13+
const jwtSecret = "Mys3cr3t";
14+
15+
const app = express();
16+
const httpServer = createServer(app);
17+
18+
app.use(bodyParser.json());
19+
20+
app.get("/", (req, res) => {
21+
res.sendFile(join(__dirname, "index.html"));
22+
});
23+
24+
app.get(
25+
"/self",
26+
passport.authenticate("jwt", { session: false }),
27+
(req, res) => {
28+
if (req.user) {
29+
res.send(req.user);
30+
} else {
31+
res.status(401).end();
32+
}
33+
},
34+
);
35+
36+
app.post("/#", (req, res) => {
37+
if (req.body.username === "john" && req.body.password === "changeit") {
38+
console.log("authentication OK");
39+
40+
const user = {
41+
id: 1,
42+
username: "john",
43+
};
44+
45+
const token = jwt.sign(
46+
{
47+
data: user,
48+
},
49+
jwtSecret,
50+
{
51+
issuer: "accounts.examplesoft.com",
52+
audience: "yoursite.net",
53+
expiresIn: "1h",
54+
},
55+
);
56+
57+
res.json({ token });
58+
} else {
59+
console.log("wrong credentials");
60+
res.status(401).end();
61+
}
62+
});
63+
64+
const jwtDecodeOptions = {
65+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
66+
secretOrKey: jwtSecret,
67+
issuer: "accounts.examplesoft.com",
68+
audience: "yoursite.net",
69+
};
70+
71+
passport.use(
72+
new JwtStrategy(jwtDecodeOptions, (payload, done) => {
73+
return done(null, payload.data);
74+
}),
75+
);
76+
77+
const io = new Server(httpServer);
78+
79+
io.engine.use((req, res, next) => {
80+
const isHandshake = req._query.sid === undefined;
81+
if (isHandshake) {
82+
passport.authenticate("jwt", { session: false })(req, res, next);
83+
} else {
84+
next();
85+
}
86+
});
87+
88+
io.on("connection", (socket) => {
89+
const req = socket.request;
90+
91+
socket.join(`user:${req.user.id}`);
92+
93+
socket.on("whoami", (cb) => {
94+
cb(req.user.username);
95+
});
96+
});
97+
98+
httpServer.listen(port, () => {
99+
console.log(`application is running at: http://localhost:${port}`);
100+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "passport-jwt-example",
3+
"version": "0.0.1",
4+
"private": true,
5+
"type": "commonjs",
6+
"description": "Example with passport and JWT (https://www.passportjs.org/packages/passport-jwt/)",
7+
"scripts": {
8+
"start": "node index.js"
9+
},
10+
"dependencies": {
11+
"body-parser": "^1.20.2",
12+
"express": "~4.17.3",
13+
"jsonwebtoken": "^9.0.2",
14+
"passport": "^0.7.0",
15+
"passport-jwt": "^4.0.1",
16+
"socket.io": "^4.7.2"
17+
},
18+
"devDependencies": {
19+
"prettier": "^3.1.1"
20+
}
21+
}

0 commit comments

Comments
 (0)