-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pass verifyClient result to connection
event
#377
Comments
+1 |
1 similar comment
+1 |
This would be a nice addition. I'm using jwt's in my const wss = new WebSocketServer({
server: server,
verifyClient: function(info, done) {
let query = url.parse(info.req.url, true).query;
jwt.verify(query.token, config.jwt.secret, function(err, decoded) {
if (err) return done(false, 403, 'Not valid token');
// Saving the decoded JWT on the client would be nice
done(true);
});
}
});
wss.on('connection', ws => {
// get decoded JWT here?
}); |
@pwnall do you store the jwt as a cookie or do you use it as part of the url? I'm implementing jwt into my code but I'm not quite sure how to deliver the token. Any suggestions? |
oh and +1 ;) |
Perhaps #1099 makes this functionality a little more public? Out of curiosity, how are you handling the invalid token on the client side? Using the 1006 error? |
@ChrisZieba I ended up verifying the JWT in my connection handler as well to get the decoded data. Did you find a better solution? |
To avoid double JWT encoding, I used global object (pertainInfosThroughConnectionProcess) where I store info’s that I want to retrieve upon opening connection. To distinguish to point right connection as key name I use JWT token itself. var pertainInfosThroughConnectionProcess = {};
const wss = new WebSocketServer({
server: server,
verifyClient: function(info, done) {
let query = url.parse(info.req.url, true).query;
jwt.verify(query.token, config.jwt.secret, function(err, decoded) {
if (err) return done(false, 403, 'Not valid token');
// Using jwt as key name and storing uid
pertainInfosThroughConnectionProcess[jwt] = decoded.uid;
// Using jwt as key name and storing numerous values in object
pertainInfosThroughConnectionProcess[jwt] = {
uid: decoded.uid,
anotherKey: 'another value',
oneMoreKey: 'one more value'
};
done(true);
});
}
});
wss.on('connection', ws => {
// Now we can use uid from global obejct pertainInfosThroughConnectionProcess
// Note: I used 'sec-websocket-protocol' to send JWT in header, so upon opening connection I can access it with ws.protocol
var uid = pertainInfosThroughConnectionProcess[ws.protocol];
// or if you saved numerous values in object
var uid = pertainInfosThroughConnectionProcess[ws.protocol].uid;
var anotherKey = pertainInfosThroughConnectionProcess[ws.protocol].anotherKey;
var oneMoreKey = pertainInfosThroughConnectionProcess[ws.protocol].oneMoreKey;
// After retrieving data, we can delete this key value as is no longer needed
// Note: delete is costly operation on object and there is way to optimize it, however for this purpose is not too bad
delete pertainInfosThroughConnectionProcess[ws.protocol];
}); |
Is there a better way to do it rather than setting up global var? |
@marcelijanowski yep: verifyClient: function({ req }, done) {
req.jwt = jwt.verify(
// ...
);
done(true);
});
wss.on('connection', (ws, req) => {
const jwt = req.jwt;
}); |
I solved this problem using the request object and a WeakMap. const userRequestMap = new WeakMap();
const server = new ws.Server({
port,
verifyClient: (info, done) => {
const user = authenticateUser(info);
userRequestMap.set(info.req, user);
done(user !== null);
},
});
server.on('connection', (connection, request) =>{
const user = userRequestMap.get(request);
}); |
+1 on this. I will most likely end up with mutating Right now the async For example, const server = new ws.Server({
verifyClient: (info, callback) => {
const verificationResult = { userId: 123 };
callback(true, verificationResult);
},
});
server.on('connection', (connection) =>{
const { userId } = connection.verificationResult;
}); I'd keep the sync implementation of This doesn't seem like a breaking change. Any concerns? |
@nilfalse that's what i'm looking for ! Are there any plans to implement that feature ? It would definetly help... |
I would be glad to investigate & contributing this feature. |
const http = require('http');
const WebSocket = require('ws');
const server = http.createServer();
const wss = new WebSocket.Server({ noServer: true });
wss.on('connection', function connection(ws, request, ...args) {
// ...
});
server.on('upgrade', async function upgrade(request, socket, head) {
// Do what you normally do in `verifyClient()` here and then use
// `WebSocketServer.prototype.handleUpgrade()`.
let args;
try {
args = await getDataAsync();
} catch (e) {
socket.destroy();
return;
}
wss.handleUpgrade(request, socket, head, function done(ws) {
wss.emit('connection', ws, request, ...args);
});
});
server.listen(8080); It gives the developer a lot of more freedom. |
Anyway, adding custom data to the |
@lpinca |
@bfailing this is the code that you would otherwise have in your |
@bfailing it is |
Guys, thank you very much for all answers :) @lpinca your answer was especially very helpful... and yes, after implementing it that ways I can understand tech debt behind |
@lpinca this is probably a dumb question, but how do I get the I have my server generate a token to authenticate the connection by doing a post first, then the websocket client sends this token along in the initial connection, but I can't seem to trigger the update part |
The websocket connection is a simple HTTP GET request with an "Upgade: Websocket" header. Look at the sample from @lpinca, the "http server" he creates receives the websocket client connections |
@lpinca I'm worried that the proposed client auth solution enforces I think something like @nilfalse / @adrianhopebailie's solution would be more clear and reduce boilerplate that everyone wanting to do client auth will eventually need to write. What is your main concern with something like #1612? Do you think there is an in-between solution that allows sync connections to be made while also allowing a dev to pass custom args into the 'connect' event from One idea is to allow Contrived Async Exampleasync authenticateUser(info) {
...
}
const server = new ws.Server({
verifyClient: (info) => {
return authenticateUser(info);
}
}); Contrived Sync Exampleconst users = {
one: 'userOneId',
two: 'userTwoId',
};
const server = new ws.Server({
verifyClient: (info) => {
return users[info.req.headers.user];
}
}); |
A good alternative is the one proposed by @zoltan-mihalyi here #377 (comment). Adding stuff to the |
@lpinca Thanks for the reply! I agree that the solution proposed by @zoltan-mihalyi is good and fits most use cases. If that is a "supported" solution, then |
I had this same question as @jplymale-jt
The answer from @lpinca was the examples in this thread wouldn't work.
Is there some other way to prevent an upgrade if it is instantiated with an http |
|
Hi. I just want to be a bit pedantic and mention that WebSockets do not have CORS. Any website can open WebSocket connections towards other websites. And with CORS it's not up to the server to refuses a connection but to the client. Preventing a WebSocket upgrade if the origin does not match is fine though, it's just not CORS. |
Taking cue from #377 (comment) I was able to pass decoded JWT token from verifyClient by setting the value into request.headers object and retrieving it in connection event's request object. I am not sure if this is the right way to do it, but this works.
|
Why not just attach the object to the request with som resonably named property? This is what JS is made for 😃 |
From my socket connection, I'm trying to set a jwt and send it back as an http response, or somehow set the cookie of the client. Am I in the right thread? Is there a recommended method for this? |
I think this is what |
@sdrsdr Thank you, I will give it a try. |
You create websocket connection to pas messages between peers a browser an a server or two servers. The establishment of the connection folows fixed rules but after that you can send anything you like to your peer, including the JWT token, and the peer can handle it as it's most cinvinient |
Hi. Sorry, probably stupid question, but It is suggested to use
It is destroyed, but how to get On client there is simple code.
Dev console (Network/WS) has Request, but no Response tab |
@rooton You can find a bunch of examples: Suggested handleUpgrade and connection flow |
Hello @trasherdk: Thanks for the examples. Out of curiosity, I checked your repo https://github.com/trasherdk/ws and saw that you mention:
Could you please share the motivation for your branch? Thanks. |
Thank you, but you are just copy/paste example. And my question is about socket.write. It is impossible to read message on client side to ensure thats an auth error. |
my 5 cents: verifyClient is the only place to deploy connection rate limits, even tough it is not in the spec it is the ideal place to ban connections with 429 before authentication |
@farr64 The reason for my clone/fork is test snippets It's pretty much answers to other peoples issues, routed into folders on my mail-server. This way I don't have to look for answers in closed issues, but have a collection of stuff I find interesting. The |
@constantind You are probably right about the rate-limit and verifyClient thingy. It makes sense to do that as early as possible. |
@trasherdk wrote:
I'm trying to figure out how to get an I've moved the authentication handling inside the httpServer.on("upgrade", (request, socket, head) => {
wss.handleUpgrade(request, socket, head, (ws) => {
const authResult = authenticate();
if (!authResult.ok) {
ws.close(4000, "authentication failed");
}
wss.emit("connection", ws, request);
});
}); I don't know if there are real costs/drawbacks to handling it here and not a level above in the |
@samal-rasmussen did you ever determine if there were any cons associated with this? I'm also in the same boat and would love to know what's the best practice here! |
Does anyone know how do I get the server response (handshake response) in the client? I can only get 1006 (abnormal closure) in the EDIT: I had to use the |
Works fine so far 🤷♂️ |
@lpinca Can you comment on the approach @samal-rasmussen is using to actually return failure codes back to a client? I've struggled to see the point of writing an HTTP response code to the socket before destroying it in the http server Yet such a pattern (writing a response code to the socket) seems to be used in all of the documentation involving authentication. |
That is "ok" but in that case the authentication happens after the WebSocket connection is established. The |
Yes, that appears to be the trade-off. So are you confirming that there is no way to return an error code to the client (at least in a way that a browser can see it) without first establishing a websocket connection? |
In the browser client, no. Other clients (like |
Yes. This also means that you cannot assume that you are authenticated when you get the open event on the client. The client must wait for a message from the server than confirms the validation first. |
I'm doing some expensive work in
verifyClient
and I'd like to reuse the result in theconnection
event handler.I'm currently using the fact that the undocumented
WebSocket
propertyupgradeReq
is the same request asinfo.req
inverifyClient
, and I'm modifying the request. This feels dirty though.Will you please consider allowing any truthy
verifyClient
result, and passing it into theconnection
event handler?If this seems reasonable, I'd be glad to prepare a pull request.
The text was updated successfully, but these errors were encountered: