Securing a websocket connection is a mess.
A good solution needs to
- work from the browser
- be hardened against CSRF
- be stateless
- work on IE 11 :(
To that end, the following should work:
- assuming the user is already authenticated (and that the site doesn't already have CSRF or XSS vulnerabilities)
- make a "websocket preauth" request to the backend from js using the site's normal auth
- backend returns a CSRF token in the response body and sets a "websocket auth" cookie with
SameSite=Strict
in the response headers - attempt to establish a websocket connection with the backend, with the addition of the CSRF token in a query parameter
- the backend checks
- that the websocket auth cookie and CSRF token are valid
- that the value of the
Origin
header matches an approved domain
- the backend sends a response and upgrades the connection to use websockets
- the websocket auth cookie MUST uniquely identify the user and include any authz scopes
- the websocket auth cookie should be short lived (< 60s)
- the CSRF should identify the websocket auth cookie
- a JWT with a
sub
field with a value that identifies the websocket auth cookie would be a good choice
- a JWT with a
- you should set the
Secure
attribute when setting the websocket auth cookie, but in theory it doesn't matter since the cookie can't be used without the CSRF token
Websockets don't
- have SOP
- allow headers to be set on the handshake request
therefore
- the CSRF token needs to be put in a query parameter instead of a header
- the
Origin
header needs to be checked explicitly, the backend can't assume that any CORS settings will take effect
also
- the websocket auth cookie can be short-lived since the websocket can continue to be used after it expires