Categories
header http javascript websocket

HTTP headers in Websockets client API

322

Looks like it’s easy to add custom HTTP headers to your websocket client with any HTTP header client which supports this, but I can’t find how to do it with the web platform’s WebSocket API.

Anyone has a clue on how to achieve it?

var ws = new WebSocket("ws://example.com/service");

Specifically, I need to be able to send an HTTP Authorization header.

10

  • 25

    I think a good solution is to allow the WebSocket to connect without authorization, but then block and wait on the server to recieve authorization from the webSocket which will transmit authorization information in its onopen event.

    – Motomotes

    Dec 28, 2015 at 15:57

  • The suggestion by @Motes seems to be the best fit. It was very easy to make an authorization call from onOpen which allows you to accept/reject the socket based on the authorization response. I originally attempted sending auth token in Sec-WebSocket-Protocol header but that feels like a hack.

    Oct 29, 2017 at 3:59

  • 1

    @Motes Hi, could you explain the “block and wait on the server” part ? you mean something like don’t process any messages till there’s a “auth” message ?

    – Himal

    Aug 18, 2019 at 4:44

  • @Himal, yes the server design must not send data or accept any other data than authorization in the beginning of the connection.

    – Motomotes

    Aug 18, 2019 at 10:37

  • @Motes Thanks for the reply. I was bit confused by the blocking part, becasue to my understanding you can’t block the initial connect request. I’m using Django channels on the back end and I’ve designed it to accept the connection on connect event. it then sets an “is_auth” flag in the receive event (if it sees a valid auth message). if the is_auth flag isn’t set and it’s not an auth message then it closes the connection.

    – Himal

    Aug 18, 2019 at 14:40

322

Updated 2x

Short answer: No, only the path and protocol field can be specified.

Longer answer:

There is no method in the JavaScript WebSockets API for specifying additional headers for the client/browser to send. The HTTP path (“GET /xyz”) and protocol header (“Sec-WebSocket-Protocol”) can be specified in the WebSocket constructor.

The Sec-WebSocket-Protocol header (which is sometimes extended to be used in websocket specific authentication) is generated from the optional second argument to the WebSocket constructor:

var ws = new WebSocket("ws://example.com/path", "protocol");
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);

The above results in the following headers:

Sec-WebSocket-Protocol: protocol

and

Sec-WebSocket-Protocol: protocol1, protocol2

A common pattern for achieving WebSocket authentication/authorization is to implement a ticketing system where the page hosting the WebSocket client requests a ticket from the server and then passes this ticket during WebSocket connection setup either in the URL/query string, in the protocol field, or required as the first message after the connection is established. The server then only allows the connection to continue if the ticket is valid (exists, has not been already used, client IP encoded in ticket matches, timestamp in ticket is recent, etc). Here is a summary of WebSocket security information: https://devcenter.heroku.com/articles/websocket-security

Basic authentication was formerly an option but this has been deprecated and modern browsers don’t send the header even if it is specified.

Basic Auth Info (Deprecated – No longer functional):

NOTE: the following information is no longer accurate in any modern browsers.

The Authorization header is generated from the username and password (or just username) field of the WebSocket URI:

var ws = new WebSocket("ws://username:[email protected]")

The above results in the following header with the string “username:password” base64 encoded:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

I have tested basic auth in Chrome 55 and Firefox 50 and verified that the basic auth info is indeed negotiated with the server (this may not work in Safari).

Thanks to Dmitry Frank’s for the basic auth answer

21

  • 71

    I’ve come across the same problem. Too bad that these standards are so poorly integrated. You’d expect that they look at the XHR API to find requirements (since WebSockets and XHR are related) for the WebSockets API, but it seems they are just developing the API on an island by itself.

    May 1, 2012 at 14:26

  • 4

    @eleotlecram, join the HyBi working group and propose it. The group is open to the public and there is ongoing work for subsequent versions of the protocol.

    – kanaka

    May 1, 2012 at 18:59

  • 7

    @Charlie: if you fully control the server, that’s one option. The more common approach to is generate a ticket/token from your normal HTTP server and then have the client send the ticket/token (either as a query string in the websocket path or as the first websocket message). The websocket server then validates that the ticket/token is valid (hasn’t expired, hasn’t already been used, coming from same IP as when created, etc). Also, I believe most websockets clients support basic auth (may not be enough for you though). More info: devcenter.heroku.com/articles/websocket-security

    – kanaka

    Jun 4, 2014 at 17:57

  • 3

    I guess its by design. I am under the impression that the implementation is intentionally borrowing from HTTP but keep them separated as much as possible by design. The text in the specificatio continues: “However, the design does not limit WebSocket to HTTP, and future implementations could use a simpler handshake over a dedicated port without reinventing the entire protocol. This last point is important because the traffic patterns of interactive messaging do not closely match standard HTTP traffic and can induce unusual loads on some components.”

    – user1441149

    Sep 14, 2016 at 18:54


  • 4

    Unfortunately this doesn’t seem to work in Edge. Thanks, MS :/

    – sibbl

    Feb 21, 2017 at 11:07

81

More of an alternate solution, but all modern browsers send the domain cookies along with the connection, so using:

var authToken = 'R3YKZFKBVi';

document.cookie="X-Authorization=" + authToken + '; path=/';

var ws = new WebSocket(
    'wss://localhost:9000/wss/'
);

End up with the request connection headers:

Cookie: X-Authorization=R3YKZFKBVi

12

  • 9

    what if the WS server URI is different from client URI?

    – Danish

    May 7, 2020 at 0:11

  • 11

    @Danish Well then that doesn’t work, since you cannot set cookies for other domains client side

    – Tofandel

    Jul 3, 2020 at 20:12

  • 5

    But, you can set up an HTTP service that sets a session cookie on the relevant path, and call that before starting your websocket. Call, say, https://example.com/login, and have the response set a cookie on /wss then new WebSocket("wss://example.com/wss") will start its handshake request with the relevant cookie. Note that the devtools might not show the cookie, but it should still be sent.

    – Coderer

    Jul 9, 2020 at 12:52

  • 9

    Websocket requests are not subject to the same origin policy. Sending authentication as a cookie will open up your application to hijacking. See christian-schneider.net/CrossSiteWebSocketHijacking.html

    Oct 19, 2020 at 18:43

  • 5

    Christian Schneider (the author of the article above) suggests using CSRF-Tokens to protect initial HTTP handshake, when using Authentication Cookie: ws = new WebSocket("wss://example.com/wss?csrftoken=<token>")

    Mar 30, 2021 at 10:31


38

HTTP Authorization header problem can be addressed with the following:

var ws = new WebSocket("ws://username:[email protected]/service");

Then, a proper Basic Authorization HTTP header will be set with the provided username and password. If you need Basic Authorization, then you’re all set.


I want to use Bearer however, and I resorted to the following trick: I connect to the server as follows:

var ws = new WebSocket("ws://[email protected]/service");

And when my code at the server side receives Basic Authorization header with non-empty username and empty password, then it interprets the username as a token.

11

  • 13

    I am trying the solution suggested by you. But I am not able to see the Authorization header being added to my request. I have tried it using different browsers e.g. Chrome V56, Firefox V51.0 I am running my server on my localhost. so the websocket url is “ws://myusername:[email protected]:8080/mywebsocket”. Any idea what might be wrong? Thanks

    Mar 11, 2017 at 21:15


  • 6

    Is it safe to transfer token through url?

    May 4, 2017 at 18:02

  • 9

    I agree with @LearnToLive – I used this with wss (e.g. wss://user:[email protected]/ws) and got no Authorization header on the server side (using Chrome version 60)

    – user9645

    Aug 4, 2017 at 13:59

  • 6

    I have the same issue as @LearnToLive and @user9645; neither chrome nor firefox are adding the authorization header when the URI is in the wss://user:[email protected] format. Is this not supported by browsers, or is something going wrong with the handshake?

    Feb 2, 2018 at 20:42

  • 11

    The use of these urls http://username:[email protected] is depreciated. developer.mozilla.org/en-US/docs/Web/HTTP/Authentication. Maybe thats the reason it does not work with websockets either!

    – Dachstein

    Aug 19, 2018 at 18:14