I need to send a cookie with websocket handshake request to ensure the load balancer routes the request to a specific backend. This works fine in Firefox, Safari, and websocket-sharp, but I cannot get Chrome to send cookies with the websocket handshake request.
I enabled sticky sessions in my load balancer (Traefik) and it "just worked" on Firefox and Safari with my existing SockJS code.
The first request has no cookie, and the load balancer sets one on the handshake response (101 switching protocols). Subsequent websocket handshake requests send the cookie, and the resulting websocket connection is established to the correct backend.
In my websocket-sharp client, I explicitly set the cookie before opening the connection, and it works as expected.
Chrome never sends cookies with websocket handshake requests. I tried existing SockJS, with cookies set by the load balancer on other requests or set explicitly in the document making the websocket request immediately before sending the request.
I tried simple key=val
cookies, and cookies with various other combinations of options set, e.g. path
, domain
, max-age
, secure
, samesite
, etc.
In the Chrome dev tools console on any site (e.g. https://www.google.com), execute:
document.cookie = 'key=val'
new WebSocket('wss://www.google.com')
Note that the scheme must be wss
if viewing a page over https
, or ws
when viewing a page over http
. Also, the domain and port are identical in the page being viewed and the URL for the websocket, as noted in the origin
header in the generated request.
Inspect the resulting request (400 bad request - I just care about the request that gets generated for this test, not the result) and it shows:
GET wss://www.google.com/ HTTP/1.1
Host: www.google.com
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: https://www.google.com
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Sec-WebSocket-Key: HrtpryMAlu5yjGCNgxzcpw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Do the same thing in Firefox, and the cookie is sent with the handshake request:
Host: www.google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Sec-WebSocket-Version: 13
Origin: https://www.google.com
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: QvNsHgLE5znjaUG04RFdPA==
DNT: 1
Connection: keep-alive, Upgrade
Cookie: <SNIPPED>; key=val
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
And Safari:
Connection: Upgrade
Host: www.google.com
Origin: https://www.google.com
Cookie: key=val; <SNIPPED>
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: zQEpYp+yzf5EQmQSb71B6g==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15
I expect the generated requests to include all document cookies known to the browser for the matching origin.
I found several references online that seem to indicate this should be the case for "modern browsers, including Chrome":
I found one reference that seems to indicate Chrome does not send cookies with the handshake request:
I could not find any official documentation or change in Chrome to explain the observed behaviour.
Update 2: I'll copy what I discovered via the bug report:
Using the UI to block cookies and edit the whitelist exception for my domain (same location as the option to disable cookies) I found the following:
wss://example.com is "Not a valid web address" and can't be added.
Entering *:// for a scheme or :* for a port is possible but gets removed from the string, resulting in:
*://[*.]example.com:443 -> [*.]example.com:443 - doesn't work
*://[*.]example.com:* -> [*.]example.com - works
*://example.com:* -> example.com - works
Update 1: I submitted a bug report and it's been confirmed:
https://bugs.chromium.org/p/chromium/issues/detail?id=947413
What worked for me was allowing cookies globally in the settings.
Yes, this is very workaroundy since I would never call allowing every cookie ever made into your browser an acceptable solution, but I'm fairly sure it's a bug. I know it used to work not that long ago with cookie blocking + whitelisting, but some version broke it while I left it alone for x number of weeks. Sorry, don't know which... I don't use Chrome so I don't really care, but maybe those who do can do something like profiles in Firefox and create a development one with this option enabled and just never go to Facebook.
To be specific, in my version of Chrome it's under the very narrow hamburger menu -> Settings -> Advanced -> Content Settings... -> Cookies -> First toggle option (switches between "Allow sites to save and read cookie data (recommended)" and "Blocked"). Or click the cookie icon in the address bar and click Manage.
I'm using the Linux version of Chrome version 73.0.3683.86 (Official Build) (64-bit) in Debian 9. It looks like you're using Mac OS, so maybe this will help you too if these versions are somewhat related. This problem doesn't seem to occur in the Windows version, but then I'm not the one using it or testing it myself and it could be older (I can edit later when I find out). It also works in Android (version 73.0.3683.90), but that one doesn't seem to have the option to block cookies in the first place.
I also tried all kinds of settings and I'm not sure of the quintuple-like word modifier for checking things 14000 times, but it's been many days trying to figure this out. I've analyzed headers and code at both ends over and over and thought it might be the cookie domain/path/expiry/httponly/secure/samesite, mismatched domains, certificate problems, server settings, etc, etc, etc, but no, it's one stupid checkbox. At least I can carry on and don't have to wait for Google to fix it...
It's likely that the cookie is sent actually inside the WebSocket Request header, just not showing up in dev-tool. It can be tracked by Chrome NetLog. From what they suggested in this Chromium issue:
Cookies are filtered from the headers shown in devtools intentionally. This is because they are passed through the renderer, which shouldn't have access to HttpOnly cookies.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With