转到主要内容

How to use with JSON Web Tokens

信息

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.

It is often used for authentication, because of its small overhead and its ability to be easily used across different domains.

More information here.

Let's start from a basic application:

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);

httpServer.listen(port, () => {
console.log(`application is running at: http://localhost:${port}`);
});
备注

In this example, we manually create the token in the /login handler, but it might come from somewhere else in your own application.

On the client side, the token is included in the Authorization header:

const socket = io({
extraHeaders: {
authorization: `bearer ${myToken}`
}
});
危险

This only works if HTTP long-polling is enabled and used first, as the browsers do not provide a way to provide additional headers for WebSocket connections:

// THIS WON'T WORK
const socket = io({
transports: ["websocket"],
extraHeaders: {
authorization: `bearer ${myToken}`
}
});

Sharing the user context

The user context can be shared with the Socket.IO server by calling:

io.engine.use((req, res, next) => {
const isHandshake = req._query.sid === undefined;
if (isHandshake) {
passport.authenticate("jwt", { session: false })(req, res, next);
} else {
next();
}
});
提示

The isHandshake check ensures that the middleware is only applied to the first HTTP request of the session.

You'll now have access to the user object:

io.on("connection", (socket) => {
const user = socket.request.user;
});

Manual parsing

In the example above, we use the passport-jwt package, but you can totally verify the bearer token manually with the jsonwebtoken package:

io.engine.use((req, res, next) => {
const isHandshake = req._query.sid === undefined;
if (!isHandshake) {
return next();
}

const header = req.headers["authorization"];

if (!header) {
return next(new Error("no token"));
}

if (!header.startsWith("bearer ")) {
return next(new Error("invalid token"));
}

const token = header.substring(7);

jwt.verify(token, jwtSecret, (err, decoded) => {
if (err) {
return next(new Error("invalid token"));
}
req.user = decoded.data;
next();
});
});

Using the user ID

You can use the user ID to make the link between Express and Socket.IO:

io.on("connection", (socket) => {
const userId = socket.request.user.id;

// the user ID is used as a room
socket.join(`user:${userId}`);
});

Which allows you to easily broadcast an event to all the connections of a given user:

io.to(`user:${userId}`).emit("foo", "bar");

You can also check whether a user is currently connected:

const sockets = await io.in(`user:${userId}`).fetchSockets();
const isUserConnected = sockets.length > 0;

That's it for the compatibility with JSON Web Tokens. Thanks for reading!

The complete example can be found here.

提示

You can run this example directly in your browser on: