Skip to content

Commit

Permalink
docs: add example with JWT
Browse files Browse the repository at this point in the history
Related: #4910
  • Loading branch information
darrachequesne committed Jan 13, 2024
1 parent d943c3e commit 914a8bd
Show file tree
Hide file tree
Showing 12 changed files with 897 additions and 0 deletions.
41 changes: 41 additions & 0 deletions examples/passport-jwt-example/README.md
Original file line number Diff line number Diff line change
@@ -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();
}
});
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 154 additions & 0 deletions examples/passport-jwt-example/cjs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Passport JWT example</title>
</head>
<body>
<div id="login-panel" style="display: none">
<p>Not authenticated</p>
<form id="login-form">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" value="john" />
<br/>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" value="changeit" />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
</div>

<div id="home-panel" style="display: none">
<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="name"></span></td>
</tr>
</tbody>
</table>

<form id="logout-form">
<div>
<input type="submit" value="Log out" />
</div>
</form>
</div>

<script src="/socket.io/socket.io.js"></script>
<script>
const loginPanel = document.getElementById('login-panel');
const homePanel = document.getElementById('home-panel');
const loginForm = document.getElementById('login-form');
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
const statusSpan = document.getElementById('status');
const socketIdSpan = document.getElementById('socket-id');
const usernameSpan = document.getElementById('name');
const logoutForm = document.getElementById('logout-form');

let socket;

async function main() {
const token = localStorage.getItem('token');

if (!token) {
return showLoginPanel();
}

const res = await fetch('/self', {
headers: {
authorization: `bearer ${token}`
}
});

if (res.status === 200) {
showHomePanel();
} else {
showLoginPanel();
}
}

function showHomePanel() {
loginPanel.style.display = 'none';
homePanel.style.display = 'block';

// this will only work if HTTP long-polling is enabled, since WebSockets do not support providing additional headers
socket = io({
extraHeaders: {
authorization: `bearer ${localStorage.getItem('token')}`
}
});

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 = '-';
});
}

function showLoginPanel() {
loginPanel.style.display = 'block';
homePanel.style.display = 'none';
}

loginForm.onsubmit = async function (e) {
e.preventDefault();

const res = await fetch('/login', {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
username: usernameInput.value,
password: passwordInput.value
})
})

if (res.status === 200) {
const { token } = await res.json();
localStorage.setItem('token', token);

showHomePanel();
} else {
passwordInput.value = '';
}
}

logoutForm.onsubmit = function (e) {
e.preventDefault();

socket.disconnect();
localStorage.removeItem('token');

showLoginPanel();
}

main();
</script>
</body>
</html>
100 changes: 100 additions & 0 deletions examples/passport-jwt-example/cjs/index.js
Original file line number Diff line number Diff line change
@@ -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}`);
});
21 changes: 21 additions & 0 deletions examples/passport-jwt-example/cjs/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading

0 comments on commit 914a8bd

Please sign in to comment.