albert
albert

Reputation: 1348

Websocket 'Sec-WebSocket-Accept' header mismatch between ReactJS client and Node.js server

I'm writing an application using WebSockets with a React client on port 8080 (run using Webpack devServer) and Node server and sockets on port 5000. However, the initial handshake always fails with an error: WebSocket connection to 'ws://localhost:5000/' failed: Error during WebSocket handshake: Incorrect 'Sec-WebSocket-Accept' header value

To make sure, I check the request and response of the React app using Chrome devtools, and see the following: enter image description here

While on my Node server, I logged the sec-websocket-accept header accept key, as well as the headers for my response, and got the following:

enter image description here

It looks like that indeed, the keys don't match. In fact, they don't seem to be the same keys at all. Is there something in between the React client and Node server (like the Webpack devserver that I'm using for React) that's changing them?

My React code:

    componentDidMount(){
        this.socket = new WebSocket('ws://localhost:5000', ['json']);
        this.socket.onerror = err => {
            console.log(err)
        }
        this.socket.onmessage = e => {
            let res = JSON.parse(e.data);
            console.log(e, res);
            let copyArr = [...this.state.message]
            copyArr.push(res);

            this.setState({
                message: copyArr
            });
        }
    }

My Node.js code:

const server = http.createServer();

server.on('upgrade', (req, socket) => {

    if(req.headers['upgrade'] !== "websocket"){
        socket.end('HTTP/1.1 400 Bad Request');
        return;
    }

    const acceptKey = req.headers['sec-websocket-key'];
    const acceptHash = generateValue(acceptKey);

    console.log('accepkey', acceptKey, 'hash', acceptHash);

    const resHeaders = [ 'HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', `Sec-WebSocket-Accept: ${acceptHash}` ];

    console.log(resHeaders);

    let protocols = req.headers['sec-websocket-protocol'];
    protocols = !protocols ? [] : protocols.split(',').map(name => name.trim());

    if(protocols.includes('json')){
        console.log('json here');
        resHeaders.push(`Sec-WebSocket-Protocol: json`);
    }

    socket.write(resHeaders.join('\r\n') + '\r\n\r\n');
})

function generateValue(key){
    return crypto
      .createHash('sha1')
      .update(key + '258EAFA5-E914–47DA-95CA-C5AB0DC85B11', 'binary')
      .digest('base64');
}

Upvotes: 1

Views: 4246

Answers (2)

albert
albert

Reputation: 1348

For anyone wondering, the culprits causing the issues, in my case, were the extra new lines and returns I added after writing my headers in my Node.js server. Taking them out and doing:

socket.write(resHeaders.join('\r\n'));

instead of:

socket.write(resHeaders.join('\r\n') + '\r\n\r\n');

solved the handshake mismatch for me.

Upvotes: 0

ottomeister
ottomeister

Reputation: 5828

The correct Accept hash for a key of 'S1cb73xifMvqiIpMjvBabg==' is 'R35dUOuC/ldiVp1ZTchRsiHUnvo='.

Your generateValue() function calculates an incorrect hash because it has an incorrect character in the GUID string '258EAFA5-E914–47DA-95CA-C5AB0DC85B11'. If you look very carefully you'll see that the second dash, in '...14–47...', is different from the other dashes. It should be a plain ASCII dash or hyphen with character code 45, but in fact it is a Unicode en-dash with character code 8211. That different character code throws off the calculation.

Fixing that character will make your WebSocket client much happier.

Upvotes: 4

Related Questions