aedes
aedes copied to clipboard
[feat] New example showing websocket authentication via HTTP form
Not exactly a feature request, but: the supplied examples are very simple and didn't cover a use case I wanted, which was to allow a WebSocket connection to pre-log-in with an HTTP form and store the session details in a cookie, rather than by using the username/password in MQTT CONNECT
. This way the login session can survive a page reload.
I've trimmed this down to the absolute basics and am posting here for posterity - if it forms the basis of an official example, great, but if not perhaps it will be useful for someone searching the issues.
Either way I don't require any action so please feel free to close this issue.
const aedes = require("aedes")();
const aedespd = require("aedes-protocol-decoder");
const ws = require("ws");
const http = require("http");
const uuid = require("uuid");
const cookie = require("cookie");
const httpServer = http.createServer((req, res) => {
let data = "";
req.on("data", chunk => {
data += chunk;
});
req.on("end", () => {
try {
if (req.url == "/login") {
data = JSON.parse(data);
let username = data.username;
let password = data.password;
aedes.authenticate(null, username, password, (error, success) => {
if (success) {
let session = { username: username, id: uuid.v4() };
if (!aedes._websessions) {
aedes._websessions = {};
}
aedes._websessions[session.id] = session;
res.setHeader("Set-Cookie", cookie.serialize("session", session.id));
res.writeHead(204);
res.end();
} else {
// Login failed: if user was already logged in, invalidate their session
let id = cookie.parse(req.headers.cookie || "").session;
if (id && aedes._websessions) {
delete aedes._websessions[id];
}
res.writeHead(401);
res.end("Unauthorized", "UTF-8");
}
});
} else if (req.url == "/logout") {
let id = cookie.parse(req.headers.cookie || "").session;
if (id && aedes._websessions) {
delete aedes._websessions[id];
res.writeHead(204);
res.end();
}
} else {
// Serve static files here, or...
res.writeHead(404);
res.end("Not found", "UTF-8");
}
} catch (e) {
res.writeHead(500);
res.end("Error: "+e, "UTF-8");
}
});
});
aedes.authenticate = function(client, username, password, callback) {
// If called from HTTP connection, client is null
if (client && client.req) {
let id = cookie.parse(client.req.headers.cookie || "").session;
let session = client.broker._websessions ? client.broker._websessions[id] : null;
if (session) {
// client already logged in via HTTP.
client.username = session.username;
client.session = session;
callback(null, true);
} else {
callback(new Error("Unauthorized"), false);
}
} else if (username == "admin" && password == "admin") {
callback(null, true);
} else {
callback(new Error("Unauthorized"), false);
}
}
const wss = new ws.Server({ server: httpServer });
wss.on("connection", (conn, req) => {
const stream = ws.createWebSocketStream(conn)
stream._socket = conn._socket
req.connDetails = aedespd.extractSocketDetails(stream.socket || stream)
aedes.handle(stream, req)
});
const mqttServer = require("net").createServer(aedes.handle);
mqttServer.listen(1883, function() {
console.log("listen on 1883");
});
httpServer.listen(8080, function() {
console.log("listen on 8080");
});
Feel free to open a PR to add the example
I like this - creative thinking. I'm in the process of updating a mosca production system to aedes. The very fact that we can adapt so easily to different scenarios is such a bonus - I can do far more in 40 lines of JS with aedes that I could ever do with mosquito or any commercial equivalent subpub solution.
I use JWTs to authenticate clients, but also then capture the decoded token into the client to authorize subscribe and publish. But it did occur to me that I have JWTs for web authentication against http endpoints. A solid example of how to use http authentication to refuse a ws: at the point of UPGRADE would be a useful addition to aedes examples - so that the client did not even get near MQTT if they failed the first test at establishing a ws: connection. Of course, I've not looked at client code to see if this is easy at that end....
@btsimonh Maybe checking how aedes-server-factory works could help: https://github.com/moscajs/aedes-server-factory/blob/main/index.js#L61