7

I have a Node.js server that I am sending a web socket upgrade request to. The Authorization header of this request contains login information, which I need to compare against a database entry. I'm unsure how I can stop the web socket connection from opening until after my database query callback is executed.

The following is a simplification of what I am currently doing:

var Express = require('express')
var app = Express() 
server = app.listen(app.get("port"), function () {})
server.on("upgrade", function (request, socket) {
//Query database
//On success set "authenticated" flag on request (later accessed through socket.upgradeReq)
//On failure abort connection
})

This works, but there is a brief period of time where the socket is open but I haven't verified the Authorization header, so it would be possible for a malicious user to send/receive data. I'm mitigating this risk in my implementation through the use of an "authenticated" flag, but it seems like there must be a better way.

I tried the following things, but while they seemed to intercept all requests except the upgrade ones:

Attempt #1: 
app.use(function (request, response, next) {
//Query database, only call next if authenticated
next()
})

Attempt #2:
app.all("*", function (request, response, next) {
//Query database, only call next if authenticated
    next()
})

Possibly worth noting: I do have an HTTP server as well, it uses the same port and accepts POST requests for registration and login.

Thank you for any assistance, please let me know if additional information is needed.

Arkcann
  • 618
  • 7
  • 15
  • Possible duplicate of [JSR-356: How to abort a websocket connection during the handshake?](http://stackoverflow.com/questions/21763829/jsr-356-how-to-abort-a-websocket-connection-during-the-handshake) – Paul Sweatte Sep 09 '16 at 08:10
  • 1
    @PaulSweatte this doesn't strike me as being a duplicate, given that this is a Node.js and Express question, while the other is Java/Tomcat related. That being said, I no longer work on this project so the question isn't relevant anymore. – Arkcann Oct 07 '17 at 02:09

2 Answers2

4

I'm not sure if this is correct HTTP protocol communication but it seems to be working in my case:

server.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;
  }
  //...
});
user1195883
  • 654
  • 4
  • 19
  • I guess that will work, as you do not include the "magic headers" that denote that the WebSocket Handshake worked out - so the client will fail the WebSocket connect. – stolsvik Mar 22 '20 at 14:11
  • socket.close() is not necessary and also not defined! – Ali80 Jun 25 '22 at 09:20
3

verifyClient is implemented for this purpose!

const WebSocketServer = require('ws').Server
const ws = new WebSocketServer({
    verifyClient: (info, cb) => {
        const token = info.req.headers.token
        if (!token)
            cb(false, 401, 'Unauthorized')
        else {
            jwt.verify(token, 'secret-key', (err, decoded) => {
                if (err) {
                    cb(false, 401, 'Unauthorized')
                } else {
                    info.req.user = decoded
                    cb(true)
                }
            })
        }
    }
})

src: Websocket authentication in Node.js using JWT and WS

Ali80
  • 6,333
  • 2
  • 43
  • 33
  • 1
    Nice - it looks like back when I originally answered this question, `verifyClient` wasn't available (at least with a cursory look through the WS github repo), but I'm glad that it's much easier now. Thanks for the answer! – Arkcann Jun 27 '22 at 12:09
  • @Arkcann I also couldn't find it before! – Ali80 Jun 27 '22 at 12:16