websockets / ws

Simple to use, blazing fast and thoroughly tested WebSocket client and server for Node.js
MIT License
21.56k stars 2.42k forks source link

Pass verifyClient result to `connection` event #377

Open pwnall opened 9 years ago

pwnall commented 9 years ago

I'm doing some expensive work in verifyClient and I'd like to reuse the result in the connection event handler.

I'm currently using the fact that the undocumented WebSocket property upgradeReq is the same request as info.req in verifyClient, and I'm modifying the request. This feels dirty though.

Will you please consider allowing any truthy verifyClient result, and passing it into the connection event handler?

If this seems reasonable, I'd be glad to prepare a pull request.

gladius commented 2 years ago

Taking cue from https://github.com/websockets/ws/issues/377#issuecomment-416248904 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.

const { WebSocketServer } = require('ws');

const wss = new WebSocketServer({
  port: 8080, verifyClient: async ({ req }, cb) => {
    const { headers } = req;
    if (headers.token) {
      const decodedToken = await decodeToken(headers.token);
      req.headers.decodedToken = JSON.stringify(decodedToken);
      cb(true);
    } else {
      cb(false, 401, 'Unauthorized');
    }
  }
});

wss.on('connection', (ws, req)=> {
  console.log("on Connection decodedToken ==> ", JSON.parse(req.headers.decodedToken));
});
sdrsdr commented 2 years ago

Why not just attach the object to the request with som resonably named property? This is what JS is made for 😃

Esqarrouth commented 2 years ago

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?

sdrsdr commented 2 years ago

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 headers event is for take a look at https://github.com/websockets/ws/blob/master/lib/websocket-server.js#L387 and line 352. But why go this way if you can just pass the JWT back to the client with a simple message

Esqarrouth commented 2 years ago

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 headers event is for take a look at https://github.com/websockets/ws/blob/master/lib/websocket-server.js#L387 and line 352. But why go this way if you can just pass the JWT back to the client with a simple message

@sdrsdr Thank you, I will give it a try. Meanwhile can you explain what you meant by passing JWT back to client with a simple message? What kind of message?

sdrsdr commented 2 years ago

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

rooton commented 2 years ago

Hi. Sorry, probably stupid question, but It is suggested to use

socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n")
socket.destroy()

It is destroyed, but how to get HTTP/1.1 401 Unauthorized on client?

On client there is simple code.

const ws = new WebSocket("ws://127.0.0.1:8080/")

ws.onerror = (event) => {
  console.log("WebSocket onerror", event)
}

ws.onclose = (event) => {
  console.log("WebSocket onclose", event)
}

Dev console (Network/WS) has Request, but no Response tab

trasherdk commented 2 years ago

@rooton You can find a bunch of examples: Suggested handleUpgrade and connection flow

farr64 commented 2 years ago

Hello @trasherdk:

Thanks for the examples.

Out of curiosity, I checked your repo https://github.com/trasherdk/ws and saw that you mention:

This branch is up to date with websockets/ws:master.

Could you please share the motivation for your branch?

Thanks.

rooton commented 1 year ago

@rooton You can find a bunch of examples: Suggested handleUpgrade and connection flow

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.

constantind commented 1 year ago

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

trasherdk commented 1 year ago

@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 trasherdk-snippets branch was to avoid locked issue template, but opted for discussions later :smile:

trasherdk commented 1 year ago

@constantind You are probably right about the rate-limit and verifyClient thingy. It makes sense to do that as early as possible.

samal-rasmussen commented 1 year ago

@trasherdk wrote:

@rooton You can find a bunch of examples: Suggested handleUpgrade and connection flow

I'm trying to figure out how to get an authentication failed close reason from a browser WebSocket client, just like @rooton did, and the linked examples don't answer this at all. The browser websocket doesn't produce a http response of any sort that you can inspect for status codes, so socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n") and socket.destroy() will just kill the websocket without giving any close reason to the browser websocket.

I've moved the authentication handling inside the wss.handleUpgrade like this because the ws.close call can respond with a code and reason that the websocket.onclose handler on the browser websocket will actually get.

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 httpServer.on("upgrade" handler. I would love it if anyone could share some wisdom on this.

prests commented 1 year ago

@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!

rotivleal commented 5 months ago

Does anyone know how do I get the server response (handshake response) in the client? I can only get 1006 (abnormal closure) in the close event. Is there really not a way to inspect the handshake response in this library?

EDIT: I had to use the unexpected-response event

samal-rasmussen commented 5 months ago

@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!

Works fine so far 🤷‍♂️

dlong500 commented 4 months ago

@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 upgrade handler because it doesn't appear that the client ever receives any response at all.

Yet such a pattern (writing a response code to the socket) seems to be used in all of the documentation involving authentication.

lpinca commented 4 months ago

That is "ok" but in that case the authentication happens after the WebSocket connection is established. The 'open' event is emitted on the client. If you want to prevent the connection from being established you have to close it during the handshake.

dlong500 commented 4 months ago

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?

lpinca commented 4 months ago

In the browser client, no. Other clients (like ws) might allow you to read the HTTP response.

samal-rasmussen commented 4 months ago

That is "ok" but in that case the authentication happens after the WebSocket connection is established. The 'open' event is emitted on the client. If you want to prevent the connection from being established you have to close it during the handshake.

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.