'Websockets token authentication using middleware and express in node.js

I use node.js, express and express-ws that is based on ws

Express-ws allows to create express-like endpoints for websockets.

I am looking for a solution to authenticate users in websocket connections, based on a token. Since my ws server is based on an HTTP one

const wsHttpServer = http.createServer();
wsHttpServer.listen(5001);
const expressWs = require('express-ws')(app , wsHttpServer);

and since the ws connection is based on an HTTP one that gets upgraded to a ws, WHY I cannot pass a token in my ws that the express route checks, like any other one? My logic is, send the token, check it, if it is ok, proceed to upgrade to a ws connection. So, I can reuse the token-middleware solution that I have in my HTTP connections.

In node

My ws server

const wsHttpServer = http.createServer();
wsHttpServer.listen(5001);
const expressWs = require('express-ws')(app , wsHttpServer);

//set the route

app.use('/ws', require('./routes/wsroute')); 

In that route, I would like to use the token.validate() middleware -that in HTTP connections, checks the Authorization header

router.ws('/user/:name/:id', token.validate(),  (ws, req) => { 
    console.log('ws route data : ',vessel, req.params.name, req.params.id);
});

In my client

    const socket = new WebSocket('ws://localhost',{
        path: '/user/Nick/25/',
        port: 5001, // default is 80
        protocol : "echo-protocol", // websocket protocol name (default is none)
        protocolVersion: 13, // websocket protocol version, default is 13
        keepAlive: 60,
        headers:{ some:'header', 'ultimate-question':42 } // websocket headers to be used e.g. for auth (default is none)
      });

this errors Failed to construct 'WebSocket': The subprotocol '[object Object]' is invalid

I also tried

const socket = new WebSocket('ws://localhost:5001/user/Nick/25', ["Authorization", localStorage.getItem('quad_token')]);

I dont get any errors, but I dont know how to get the Authorization "header" in node

I could

just send const socket = new WebSocket(currentUrl); with some data and include a valid token in that data. But to check it, I have to allow the connection first. I dont want that, I would like to use a middleware solution that automatically checks a token and allows or not to continue.

Questions

Please help me understand:

1 Is it possible to use a token-based, middleware-based solution in ws?

2 How to set a header with a token in a ws connection?

3 How to get that token in node?



Solution 1:[1]

1) In my experience there is no available express.js middleware and the solution i found requires to listen to the upgrade event on your http server and blocking access to your socket connection before it reaches ws routes.

2) Your browser will not allow setting additional headers during websocket connection on the client side. It will send though the cookies so you can make use of express-session to authorize on your server first the user, a cookie will be set on the browser and that cookie will be sent over during the websocket connection.

3) You can do like in this answer Intercept (and potentially deny) web socket upgrade request Copying the code here from there for your own perusal.

**wsHttpServer**.on('upgrade', function (req, socket, head) {
      var validationResult = validateCookie(req.headers.cookie);
      if (validationResult) {
        //...
      } else {
        socket.write('HTTP/1.1 401 Web Socket Protocol Handshake\r\n' +
                     'Upgrade: WebSocket\r\n' +
                     'Connection: Upgrade\r\n' +
                     '\r\n');
                     socket.close();
                     socket.destroy();
                     return;
      }
      //...
    });

Solution 2:[2]

As outlined here, it seems that it is not possible for a standard browser websocket client to handle a http error response to an upgrade request. Thus what I ended up using was something like this:

HTTPserver.on('upgrade' (req, sock, head) => {
  if (req.url === wsRoute) {
    webSocketServer.handleUpgrade(req, sock, head, ws => {
      const authenticated = validateToken(req.headers.cookie) // your authentication method
      if (!authenticated) {
        ws.close(1008, 'Unauthorized') // 1008: policy violation
        return
      }
      webSocketServer.emit('connection', ws, req)
    })
  } else {
    sock.destroy()
  }
}
      

This way we accept the connection first before closing it with an appropriate code and reason, and the websocket client is able to process this close event as required.

Solution 3:[3]

On your client side, you should pass an array of strings instead of object, but you must set a header for your HTTP response with a key and value:

key : headeSec-WebSocket-Protocol
value : corresponding protocol used in front.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Claudio Viola
Solution 2
Solution 3 the Tin Man