admin管理员组文章数量:1126348
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/service");
Specifically, I need to be able to send an HTTP Authorization header.
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.
Share Improve this question edited Mar 30, 2022 at 1:12 Domenic 113k42 gold badges226 silver badges273 bronze badges asked Dec 5, 2010 at 21:04 Julien GenestouxJulien Genestoux 32.9k22 gold badges69 silver badges94 bronze badges 12- 30 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 Commented Dec 28, 2015 at 15:57
- 1 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. – BatteryAcid Commented 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 Commented Aug 18, 2019 at 4:44
- 1 @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. – Ashwin Prabhu Commented Jun 29, 2022 at 9:29
- 1 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. – MrCranky Commented Nov 30, 2022 at 11:46
20 Answers
Reset to default 402Updated 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
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 i.e. where the client can send one-off requests and doesn't need to actively stream to the server. 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.
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
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.
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).
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 (,
) !
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.
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 Authorization
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
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:
- Headers aren't supported by WebSockets
- Headers are advised against during the HTTP -> WebSocket upgrade because CORS is not enforced
- SSL encrypts query paramaters
- Browsers don't cache WebSocket connections the same way they do with URLs
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...
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
}}}
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.
I've tried these two approaches and neither worked, hopefully this will save you time:
- Implement websockets using fetch and streams api Is it possible to implement websockets in Javascript with Streams api?
- Use WebAssembly instead of javascript Feasibility of using WebAssembly to implement a WebSockets Client
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
})
);
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
As of 2024 you can pass any http/s.request options to the websocket constructor.
See: https://github.com/websockets/ws/issues/2074
const ws = new WebSocket(url, {
headers: { foo: 'bar' }
});
From nodejs v22
now you can pass a WebSocketInit
as the second parameter. Example:
var ws = new WebSocket(url, {
protocols: ...
headers: {
'User-Agent': 'xxx',
...
},
})
Though this is not been written in MDN Docs, but nodejs chose undici as WHATWG implementation, like fetch/WebSocket
.
- https://github.com/whatwg/websockets/issues/42
- https://github.com/nodejs/undici/blob/main/docs/docs/api/WebSocket.md
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
.
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.
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);
本文标签: javascriptHTTP headers in Websockets client APIStack Overflow
版权声明:本文标题:javascript - HTTP headers in Websockets client API - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736685287a1947630.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论