HTTP headers in Websockets client API
Asked Answered
O

18

406

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.

Outskirts answered 5/12, 2010 at 21:4 Comment(12)
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.Bellini
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.Dissociation
@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 ?Juieta
@Himal, yes the server design must not send data or accept any other data than authorization in the beginning of the connection.Bellini
@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.Juieta
I guess by block I was implying reject any other traffic, or that is as to say do the auth yourself and reject any failed auth like you say.Bellini
@Motes Allowing a websocket creation by deferring auth has risks such as malicious attacks that can overload the server with lot of unauthenticated socket creations.Transfiguration
@AshwinPrabhu Any open socket is vulnerable to DDOS. Doing auth with one's own code or being a code monkey dependent on another cut and paste framework doesn't change that. I didn't say use Django, I said construe the authentication scheme oneself, having Django change or extend the websocket protocol to do the auth for one has no unique relevance to DDOS vulnerability. The socket should of course be closed if the client does not authenticate.Bellini
@Motes Essentially you mean to say you would run a background GC to sweep through all the open but unauthenticated connections? I was just calling out a potential risk in the solution that requires waiting until the first message to invalidate open connections. Not sure where Django enters this argument - maybe that's what you work with.Transfiguration
"Essentially you mean to say you would run a background GC to sweep through all the open but unauthenticated connections?" No, not even close.Bellini
Not an answer, but relevant to this discussion is this issue in the standards repo: github.com/whatwg/websockets/issues/16 where the implementers talk about why they're resisting putting in such support. Since it's been open for 5 years now, I can't see it changing any time soon. The advice over there basically boils down to: "put the token in the URL for the handshake, or do a post-connect message to provide the token." There is also surprise that people aren't (mis)using the Sec-WebSocket-Protocol header even though that clearly isn't the design intent for that header.Comestible
It is nothing less than intolerable how basic security in web technologies gets treated as a nice-to-have by standardization mechanisms. There should be one obvious way to do basic security. Lots of seriously flawed solutions floating around because there is no obvious way to do this, lots of choices to choose from, and every programmer needs to become a security expert just to do basic things safely. It is not okay. The industry needs to pull up its pants on this.Haiduk
P
381

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

Protocol answered 5/12, 2010 at 21:35 Comment(24)
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.Miculek
@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.Protocol
Would it be a bad idea to use the Protocol header values to send (for example) an authorisation hash? Reading here - #7363595 - it doesn't feel like a bad idea, but I do wonder what the implications are.Gus
@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-securityProtocol
@eleotiecram According to rfc6455: WebSocket Protocol is designed to supersede existing bidirectional communication technologies that use HTTP as a transportlayer to benefit from existing infrastructure (proxies, filtering,authentication). [...........] The WebSocket Protocol attempts to address the goals of existing bidirectional HTTP technologies in the context of the existing HTTP infrastructure; as such, it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries, even if this implies some complexity specific to the current environment.Scute
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."Scute
2016 and this is still the case :(Afterguard
If we cannot add a custom header, can we at least modify a non custom header, for instance, 'Cookie'?Rivkarivkah
There is a way to set Basic Authorization HTTP header, see my answer.Massachusetts
Unfortunately this doesn't seem to work in Edge. Thanks, MS :/Opera
ws://username:[email protected] this pattern doesn't work in Safari, but works in chrome, ffMalamute
how do i set a "connection" header? thanksHugmetight
Does Basic auth still work? I've tested Chrome and Firefox and there is no Authorization header.Licit
Basic auth won't work anymore as the approach is deprecated. developer.mozilla.org/en-US/docs/Web/HTTP/…Enmesh
When I tried to use "Sec-WebSocket-Protocol" in chrome, I got WebSocket: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received error.Enmesh
@Enmesh I've updated the answer to show that basic auth is deprecated. The reason you get that error is because the server needs to support the protocol field and answer with the protocol value that was selected.Protocol
@Protocol do you have a link to where it says basic auth is deprecated?Cockaigne
@Afterguard 2019 and this is still the case :(Naucratis
I'm working around the limitations and leveraging the protocol field as an authentication field. No different than using Authentication: Bearer TOKEN... just a different header to observe on before allowing the the WS to open up... if anyone needs details on this approach, I can paste my solution as a new answerUnderthecounter
@MattLo Does piggybacking auth token as sec protocol header work across browsers for desktop and mobile?Transfiguration
@AshwinPrabhu yep, any version the security protocol header is available as an input, you can hack it to add w/e data you want in thereUnderthecounter
@Protocol What are the pros/cons of sending the token in the params or first message? Will configuring it in the params send it in every subsequent message, as you can't change this after connection? I imagine if that's the case then that's a downer if throughput is an issue.Underskirt
@DanielGerson The URL and query parameters and other HTTP headers are only sent once when the connection is established. Where you send that really depends on what system is going to make use of it. WebSocket connections are often passed through a front-end to the actual WebSocket server. If the front-end needs the info then you would want it in the params or headers. If the backend, then either would work (depending on how the pass-through works).Protocol
@sibbl, providing arbitrary value in Protocol argument in WebSocket constructor and receiving it on backend in header sec-websocket-protocol works fine in Edge - just tested on latest version (Version 122.0.2365.63 (Official build) (arm64), macOS Ventura 13.1).Nganngc
I
94

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
Izy answered 5/2, 2018 at 9:4 Comment(12)
This does seem like the best way to pass access tokens on connection. At the moment.Lise
what if the WS server URI is different from client URI?Miyokomizar
@Miyokomizar Well then that doesn't work, since you cannot set cookies for other domains client sideFellini
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.Elliellicott
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.htmlRadiopaque
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>")Atone
You could also simply check the origin.Nidus
@Elliellicott could explain how would I achieve this pattern? I am trying to integrate with cookies but those different domains will not allow it (localhost development)Nidus
The cookie has to be set on the domain that you're connecting to, not the Origin where your script is served from. That's why I suggested having example.com/login return a Set-Cookie: Session=xyz123. Once example.com has a session cookie associated with it, new WebSocket("wss://example.com/wss/") will send the Session cookie along with the handshake. As Declan points out, though, this can lead to hijacking. If you control the backend, it'd be better to send credentials over the socket connection.Elliellicott
Underrated comment by @Coderer: Note that the devtools might not show the cookie, but it should still be sent. I've confirmed that the dev-tools do not show these cookies on websocket requests/connections! Annoying as this caused a lot of time lost thinking I was doing something wrong.Tyre
@Tyre +1, devtools on Firefox/Chrome/Opera do not show any cookie header but I will have to assume it is indeed not sent at all (contrary to Coderer saying "it should be sent", as I was not able to reproduce this attack on different domains/servers/sockets I own. Tested on mobile Android Chrome as well : no exploit.Goldenrod
@Goldenrod Odd; in my case, the dev-tools did not show the cookie being sent on the WS connection, yet it was in fact sent (I know because my NodeJS server was receiving the auth data from the WS connection). There must be some difference of the protocol used, the server tech stack, or something of that sort.Tyre
L
64

Here's a quick summary of the situation for the weary traveller stumbling upon this in 2024, and probably for a very long time after that.

Nothing has changed in the 12(!) years since this question was opened. The JavaScript WebSocket API is abandoned by all browser vendors (although the implementations do occasionally get updates), and the new specs (WebSocket Stream and WebTransport) are nowhere close to materialization. What this all means is that WebSockets are still widely used, no replacement for the broken API exists despite it being called "legacy" 7 years ago, and the problems outlined in the question are as annoying as ever, if not more.

The options for dealing with the situation (spoiler, #5 or #6 is what you want):

1. Implement authentication externally

Described in https://devcenter.heroku.com/articles/websocket-security. The client is expected to make an authenticated request to a dedicated end point that will generate and persist a short-lived token that will also be sent to the client. The client then returns this token as a URL param when opening a WebSocket. The server can validate it and accept/reject the protocol upgrade. This requires the server to implement a completely custom and stateful authentication mechanism specifically for WebSockets, which is a bridge too far in many scenarios.

2. Send auth information over WebSocket

You open a WebSocket without authenticating, then you send your auth information over WebSocket prior to doing anything else. This in theory sounds logical (and is advised by the browser vendors), but falls apart given just a cursory thought. The server is made to implement an awkward, highly stateful and entirely custom authentication mechanism that doesn't play well with anything else, on top of either having to maintain a persistent connection with a client who refuses to authenticate, leaving a door wide open for denial of service attacks, or getting into a whole new rabbit whole of enforcing rigorous time outs to prevent malicious behavior.

3. Send auth info (e.g. an access token) via a URL param

Not as terrible as it sounds, as long as SSL is enforced (wss:// not ws://) because WebSocket URLs are special and don't get saved in browser history or similar. On top of that, access tokens are normally short lived, so that also mitigates the danger. But. The server will very likely log the URL anyway at some point. Even if your server application doesn't, the framework or the (cloud) host probably will. Additionally, if you have to pass ID tokens around (like Firebase is wont to do), you might trip up on various URL length limitations as ID tokens get huge.

4. Auth via a good old cookie

Don't. WebSockets are not subject to same-origin policy (because apparently every little thing about WebSockets has to be awful) and allowing cookies would leave you wide open to CSRF attacks. Fixing this using CSRF tokens is described e.g. here but it is more difficult than taking any other approach from this list, so it is simply not even worth considering.

5. Smuggle access tokens inside Sec-WebSocket-Protocol

Since the only header a browser will let you control is Sec-WebSocket-Protocol, you can abuse it to emulate any other header. Interestingly (or rather comically), this is what Kubernetes is doing. In short, you append whatever you need for authentication as an extra supported subprotocol inside Sec-WebSocket-Protocol:

var ws = new WebSocket("ws://example.com/path", ["realProtocol", "yourAccessTokenOrSimilar"]);

Then, on the server, you add some sort of middleware that transforms the request back to its saner form before passing it further into the system. Terrible, yes, but so far the best solution. No tokens in the URL, no custom authentication save for the little middleware, no extra state on the server needed. Do not forget to include the real subprotocol, as various tools will reject a connection without one.

6. Switch to SSE (or RSocket?), if applicable

For a good number of cases, SSE might be a decent replacement. The browser EventSource API is as horribly broken as WebSocket (it can't send anything but GET requests, can't send headers either despite being regular HTTP), but! it can be easily replaced by fetch which is, for a change, a saner API. This approach works well as an alternative to WebSocket in e.g. GraphQL subscriptions, or really anywhere where full duplex isn't mandatory. And that likely covers most scenarios. RSocket could theoretically also be an option, but seeing how it's implemented via WebSockets in the browser, I don't think it actually resolves anything, but I didn't look into it deep enough to say with absolute certainty.

Lacasse answered 7/9, 2023 at 14:23 Comment(10)
Thank you for this answer! It saved me a lot of research in 2023!Khanate
@EmmaAlecrim Happy to hear that! ☺️Lacasse
Do you mind offering more details on how to setup the middleware for #5 (smuggling)? Specifically, is there an easy way to setup some kind of middleware for my nginx ingress controller in a kubernetes cluster?Progeny
@Progeny I've never done it in nginx (I did it in the application itself), but I imagine it should be achievable with a map directive and add_header (or proxy_set_header). Something like: map $http_sec_websocket_protocol $extracted_token { "~realProtocol(?<t>.*)" $t; } to get the token out of the subprotocol header, and then add_header Authorization "Bearer $extracted_token"; to recreate the usual auth header. I've never tried any of this, so take it as a guideline more than a definitive answer. Not sure if there's anything Kubernetes specific to worry about.Lacasse
I see, the problem there is that configuration snippets (as far as I know the only way to do what you suggest) is a security vulnerability (see github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/…). I ended up implementing a websocket proxy service that extracts the header and appends it using go (see https://mcmap.net/q/87589/-how-to-set-cookie-in-deno-websocket). I assume there's a better option, though.Progeny
by the way, in case any ever needs a simpler solution. You can use the auth-url in combination with the auth-response-headers to accomplish this (for reference: medium.com/@ankit.wal/…). Precisely, you can use the auth-url to hit a service which extracts the relevant header, then returns that header in the response. Then use the auth-response-headers to apply that header to the request in the Ingress. I would have used this, but my goal was associated with session affinity and this didnt' workProgeny
@Progeny Yup, read up a bit on this, and it does indeed seem like the best option.Lacasse
In various situations involving multiple websocket servers, where specific clients must establish direct communication with particular servers to streamline and avoid unnecessary routing complexities on the server side, the utilization of a sticky session cookie becomes necessary. Despite the potential security concerns associated with using cookies for authentication in the context of CORS, refraining from using cookies altogether is not a steadfast rule for websockets.Iridosmine
@kaqqao, most of the API gateway solutions support only headers for sticky sessions. Very few of them support URL parameters or something else. What do you mean "modern browsers no longer support cookies on WebSocket handshake anyway"? Latest Chrome (Version 120.0.6099.109 (Official Build) (64-bit)) I'm using right now does send all cookies into the WebSocket handshake request.Iridosmine
@Iridosmine You're right, it was a bug I was thinking of, not the regular behavior. Deleted that comment not to confuse future readers. But I still don't get why use a cookie and open the CSRF can of worms, and not use any other approach I described and dodge the issue completely?Lacasse
F
42

Sending Authorization header is not possible.

Attaching a token query parameter is an option. However, in some circumstances, it may be undesirable to send your main login token in plain text as a query parameter because it is more opaque than using a header and will end up being logged whoknowswhere. If this raises security concerns for you, an alternative is to use a secondary JWT token just for the web socket stuff.

Create a REST endpoint for generating this JWT, which can of course only be accessed by users authenticated with your primary login token (transmitted via header). The web socket JWT can be configured differently than your login token, e.g. with a shorter timeout, so it's safer to send around as query param of your upgrade request.

Create a separate JwtAuthHandler for the same route you register the SockJS eventbusHandler on. Make sure your auth handler is registered first, so you can check the web socket token against your database (the JWT should be somehow linked to your user in the backend).

Flosi answered 10/10, 2018 at 22:53 Comment(3)
This was the only secure solution I could come up with for API Gateway websockets. Slack does something similar with their RTM API and they have a 30 second timeout.Naucratis
Note that if you're creating a websocket connection from node-js (as opposed to the browser), sending an authorization header is possible. See here: github.com/apollographql/apollo-client/issues/…Tyre
so instead of adding the ability to set headers, you're meant to duplicate your entire auth stack just to authenticate a websocket? Am I missing something?Progeny
M
41

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.

Massachusetts answered 7/1, 2017 at 13:18 Comment(11)
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:mypassword@localhost:8080/mywebsocket". Any idea what might be wrong? ThanksBaseler
Is it safe to transfer token through url?Clangor
@Mergasov same thing is questionable for Basic Authentication right?Cannabin
Wouldn't the basic authentication headers be hidden if secure connection is used, while the URL would be open to everybody snooping?Treasurer
Empty/ignored username and non-empty password as token might be better because usernames might get logged.Plumbo
I agree with @Baseler - 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)Beshore
@Treasurer In a secure connection, everything including the URL is hidden inside of secure tunnel.Engedus
I have the same issue as @Baseler and @user9645; neither chrome nor firefox are adding the authorization header when the URI is in the wss://user:pass@host format. Is this not supported by browsers, or is something going wrong with the handshake?Panicstricken
same as @DavidKaczynski Anyone know why? Maybe new version drop the support?Muntin
@user9645, that is incorrect! The URL is not encrypted. Even the same in HTTPS.English
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!Deathwatch
T
32

You can not send custom header when you want to establish WebSockets connection using JavaScript WebSockets API. You can use Subprotocols headers by using the second WebSocket class constructor:

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

and then you can get the Subprotocols headers using Sec-WebSocket-Protocol key on the server.

There is also a limitation, your Subprotocols headers values can not contain a comma (,) !

Trona answered 9/3, 2016 at 11:20 Comment(4)
May a Jwt contain a comma?Hiltonhilum
I don't believe so. JWT consists of three base64-encoded payloads, each separated by a period. I believe this rules out the possibility of a comma.Dicta
I implemented this and it works - just feels weird. thanksDissociation
are you suggesting we use the Sec-WebSocket-Protocol header as alternate to the Authorization header?Biotin
T
31

You cannot add headers but, if you just need to pass values to the server at the moment of the connection, you can specify a query string part on the url:

var ws = new WebSocket("ws://example.com/service?key1=value1&key2=value2");

That URL is valid but - of course - you'll need to modify your server code to parse it.

Tiros answered 4/3, 2017 at 13:50 Comment(6)
need to be careful with this solution, the query string may be intercepted, logged in proxies etc. so passing sensitive info (users / password / authentication tokens) this way won't be secure enough.Policyholder
@Policyholder with WSS the querystring should probably be safeOleary
ws is plain text. Using ws protocol anything can be intercepted.Tiros
@SebastienLorber it is not safe to use query string it is not being encrypted the same applies to HTTPS, but since "ws://..." protocol is used, it really doesn't matter.Capwell
@Capwell the query string is encrypted, but there are a host of other reasons not to add sensitive data as URL query parameters https://mcmap.net/q/24468/-are-https-urls-encrypted/… & blog.httpwatch.com/2009/02/20/… as refsAcrolein
@Acrolein This comment is heavily underrated.Rash
P
25

For those still struggling in 2021, Node JS global web sockets class has an additional options field in the constructor. if you go to the implementation of the the WebSockets class, you will find this variable declaration. You can see it accepts three params url, which is required, protocols(optional), which is either a string, an array of strings or null. Then a third param which is options. our interest, an object and (still optional). see ...

declare var WebSocket: {
    prototype: WebSocket;
    new (
        uri: string,
        protocols?: string | string[] | null,
        options?: {
            headers: { [headerName: string]: string };
            [optionName: string]: any;
        } | null,
    ): WebSocket;
    readonly CLOSED: number;
    readonly CLOSING: number;
    readonly CONNECTING: number;
    readonly OPEN: number;
};

If you are using a Node Js library like react , react-native. here is an example of how you can do it.

 const ws = new WebSocket(WEB_SOCKETS_URL, null, {
    headers: {
      ['Set-Cookie']: cookie,
    },
  });

Notice for the protocols I have passed null. If you are using jwt, you can pass the Authorisation header with Bearer + token

Disclaimer, this might not be supported by all browsers outside the box, from the MDN web docs you can see only two params are documented. see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#syntax

Procto answered 28/9, 2021 at 17:26 Comment(0)
R
6

Totally hacked it like this, thanks to kanaka's answer.

Client:

var ws = new WebSocket(
    'ws://localhost:8080/connect/' + this.state.room.id, 
    store('token') || cookie('token') 
);

Server (using Koa2 in this example, but should be similar wherever):

var url = ctx.websocket.upgradeReq.url; // can use to get url/query params
var authToken = ctx.websocket.upgradeReq.headers['sec-websocket-protocol'];
// Can then decode the auth token and do any session/user stuff...
Radiocommunication answered 1/6, 2017 at 17:55 Comment(1)
Doesn't this just pass your token in the section where your client is supposed to request one or more specific protocols? I can get this working, no problem too, but I decided not to do this and rather do what Motes suggested and block until the auth token is sent on the onOpen(). Overloading the protocol request header seems wrong to me, and as my API is for public consumption, I think it is going to be kinda confusing for consumers of my API.Ellett
P
6

The recommended way to do this is through URL query parameters

// authorization: Basic abc123
// content-type: application/json
let ws = new WebSocket(
  "ws://example.com/service?authorization=basic%20abc123&content-type=application%2Fjson"
);

This is considered a safe best-practice because:

Proportion answered 24/11, 2022 at 18:14 Comment(0)
E
5

In my situation (Azure Time Series Insights wss://)

Using the ReconnectingWebsocket wrapper and was able to achieve adding headers with a simple solution:

socket.onopen = function(e) {
    socket.send(payload);
};

Where payload in this case is:

{
  "headers": {
    "Authorization": "Bearer TOKEN",
    "x-ms-client-request-id": "CLIENT_ID"
}, 
"content": {
  "searchSpan": {
    "from": "UTCDATETIME",
    "to": "UTCDATETIME"
  },
"top": {
  "sort": [
    {
      "input": {"builtInProperty": "$ts"},
      "order": "Asc"
    }], 
"count": 1000
}}}
Evoke answered 18/1, 2020 at 0:1 Comment(2)
It doesn't look like generic way to pass custom headers as don't see an option in their API. Is it a generic way or works only with Azure and some server side configuration?Luke
This might be a decent enough work around for some, but it's worth noting that this will add a lot of IO for particularly chatty use-cases (the headers are sent every request).Divisibility
C
2

to all future debugger - until today i.e 15-07-21

Browser also don't support sending customer headers to the server, so any such code

    import * as sock from 'websocket'
    const headers = {
        Authorization: "bearer " + token
    };
    console.log(headers);

    const wsclient = new sock.w3cwebsocket(
        'wss://' + 'myserver.com' + '/api/ws',
        '',
        '',
        headers,
        null
    );

This is not going to work in browser. The reason behind that is browser native Websocket constructor does not accept headers.

You can easily get misguided because w3cwebsocket contractor accepts headers as i have shown above. This works in node.js however.

Cleaver answered 15/7, 2021 at 7:54 Comment(0)
I
1

I've tried these two approaches and neither worked, hopefully this will save you time:

  1. Implement websockets using fetch and streams api Is it possible to implement websockets in Javascript with Streams api?
  2. Use WebAssembly instead of javascript Feasibility of using WebAssembly to implement a WebSockets Client
Ibson answered 28/7, 2023 at 13:38 Comment(0)
E
0

What I have found works best is to send your jwt to the server just like a regular message. Have the server listening for this message and verify at that point. If valid add it to your stored list of connections. Otherwise send back a message saying it was invalid and close the connection. Here is the client side code. For context the backend is a nestjs server using Websockets.

  socket.send(
      JSON.stringify({
        event: 'auth',
        data: jwt
      })
    );
Excalibur answered 13/10, 2022 at 19:54 Comment(0)
E
0

Client Side: No

Server Side: Yes

All you need to do is attach the header to the websocket after auth on the server. All future communication with that websocket will have that auth token.

Server Environment: Bun JS / Elysia

  • This should translate over to node / express, I just have not done it there.

Example:

const app = new Elysia({
  websocket: {
    idleTimeout: 180
  }
});

app.ws("/ws", {
  message: async (ws, message) => {
    // message = { action: 'LOGIN', payload: { username: 'bob', password: 'iAmB0B' }}
    if(message.action === 'LOGIN') {
      // Do auth stuff 
      const token = await callAuth(message.payload);
      if(token) {
        ws.data.headers['authorization'] = `Bearer ${token}`;
      }
    } else {
      // Verify Auth - All future calls with this socket will have this auth.
      const auth = ws.data.headers['authorization']; // `Bearer ${token}`
    }
  }
});

References:

Bun JS Websockets: https://bun.sh/docs/api/websockets

Elysia JS Websockets: https://elysiajs.com/patterns/websocket.html

Epidemiology answered 22/9, 2023 at 19:58 Comment(0)
C
-3

My case:

  • I want to connect to a production WS server a www.mycompany.com/api/ws...
  • using real credentials (a session cookie)...
  • from a local page (localhost:8000).

Setting document.cookie = "sessionid=foobar;path=/" won't help as domains don't match.

The solution:

Add 127.0.0.1 wsdev.company.com to /etc/hosts.

This way your browser will use cookies from mycompany.com when connecting to www.mycompany.com/api/ws as you are connecting from a valid subdomain wsdev.company.com.

Coprophilia answered 13/11, 2019 at 19:41 Comment(0)
P
-5

You can pass the headers as a key-value in the third parameter (options) inside an object. Example with Authorization token. Left the protocol (second parameter) as null


ws = new WebSocket(‘ws://localhost’, null, { headers: { Authorization: token }})

Edit: Seems that this approach only works with nodejs library not with standard browser implementation. Leaving it because it might be useful to some people.

Penrose answered 18/10, 2019 at 12:48 Comment(3)
Had my hopes up for a sec. There doesn't appear to be an overload taking a 3rd param in WebSocket ctor.Confectioner
Got the idea from wscat code. github.com/websockets/wscat/blob/master/bin/wscat line 261 wich uses the ws package. Thought this was an standard usage.Penrose
This doesn't work for client side and at the server this answer seems irrelevant.Nidus
W
-6

Technically, you will be sending these headers through the connect function before the protocol upgrade phase. This worked for me in a nodejs project:

var WebSocketClient = require('websocket').client;
var ws = new WebSocketClient();
ws.connect(url, '', headers);
Wingding answered 8/1, 2017 at 19:47 Comment(5)
This is for the websocket client in npm (for node). npmjs.com/package/websocket Overall this would be exactly what I am looking for, but in the browser.Loco
It's downvoted because this headers parameter in on the WebSocket protocol layer, and the question is about HTTP headers.Mallina
"headers should be either null or an object specifying additional arbitrary HTTP request headers to send along with the request." from WebSocketClient.md; therefore, the headers here is HTTP layer.Julee
Also, anyone who wants to provide custom headers should keep in mind the function signature of the connect method, described as connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]), i.e. the headers should be provided along with requestOptions, for example, ws.connect(url, '', headers, null). Only the origin string can be ignored in this case.Julee
This solution only works for the ws nodejs clientIngraham

© 2022 - 2024 — McMap. All rights reserved.