How to use IPv6 with NodeJS https.request() - javascript

I have written a program that exercises the REST API of an embedded device that my team is developing. The code that issues the https requests looks like this:
var options = {
hostname: hostAddress,
port: hostPort,
path: path,
method: methodName,
headers: {
"Content-Type": "application/json",
"Content-Length": post_data.length
}
};
// Request and callback
var request = https.request(
options,
function onResponse( response ) {
// response code happens in here ...
}
);
This code works just fine when hostAddress is an IPv4 address, e.g. "192.168.1.13". But we've recently added IPv6 support onto the embedded device and that needs to be tested as well. However, the same code above does not work when the hostAddress is an IPv6 address.
The documentation for https.request() [here: https://nodejs.org/api/https.html ] doesn't say anything about IPv6. I have tried using each of the following values for the hostAddress, but without success.
hostAddress = "fe80::9eb6:54ff:fe90:8b70%eth0";
hostAddress = "[fe80::9eb6:54ff:fe90:8b70%eth0]";
hostAddress = "fe80::9eb6:54ff:fe90:8b70";
hostAddress = "[fe80::9eb6:54ff:fe90:8b70]";
I know the IPv6 address is correct -- I copied and pasted it from an ifconfig call on the embedded system host.
Can anyone tell me what I'm missing here?

Node version 0.12 doesn't support IPv6 on DNS binding, and since request utilizes that, it also won't work.
Per this thread, it's scheduled for implementation in v0.13.
Update
As of 2015, this appears to now be implemented in the main nodejs/node project (the re-amalgamation of Node.js and io.js).
http = require('http');
server = http.createServer();
server.listen(9000, '::');

Related

sending a websocket request through a proxy server in nodeJS [duplicate]

Generalizing that would be the question... how to make websockets to go through a proxy in node.js?
In my particular case I'm using pusher.com with the node.js client library they recommend. Looking inside the code I would like to know some hints on what I should change in order to make this library to work with a proxy... you can take a look in the code here
Maybe I should somehow replace or modified the websockets module that is being used by the library?
EDIT
Thanks for your answers/comments! A couple of things to take into consideration (excuse me if I'm wrong with some/all of them, just learning):
I don't want to create a proxy server. I just want to use an existent proxy server within my company in order to proxified my websockets requests (particularly pusher.com)
Just to let you know, if I use a proxifier like the one for windows Proxifier and I set up the rule to inspect for all connections to port 443 to go through the proxy server proxy-my.coporate.com:1080 (type SOCKS5) it works like a charm.
But I don't want to go this way. I want to programatically configuring this proxy server within my node js code (even if that involved to modified the pusher library I mentioned)
I know how to do this for HTTP using Request module (look for the section that mentions how to use a proxy).
I want a similarly thing for websockets.
From
https://www.npmjs.com/package/https-proxy-agent
var url = require('url');
var WebSocket = require('ws');
var HttpsProxyAgent = require('https-proxy-agent');
// HTTP/HTTPS proxy to connect to
var proxy = process.env.http_proxy || 'http://168.63.76.32:3128';
console.log('using proxy server %j', proxy);
// WebSocket endpoint for the proxy to connect to
var endpoint = process.argv[2] || 'ws://echo.websocket.org';
var parsed = url.parse(endpoint);
console.log('attempting to connect to WebSocket %j', endpoint);
// create an instance of the `HttpsProxyAgent` class with the proxy server information
var options = url.parse(proxy);
var agent = new HttpsProxyAgent(options);
// finally, initiate the WebSocket connection
var socket = new WebSocket(endpoint, { agent: agent });
socket.on('open', function () {
console.log('"open" event!');
socket.send('hello world');
});
socket.on('message', function (data, flags) {
console.log('"message" event! %j %j', data, flags);
socket.close();
});
Using a proxy for websockets should work roughly the same as for https connections; you should use the CONNECT method. At least that's what both the HTTP and HTML5 specs say. So if your proxy implements CONNECT, you're good to go.
Try node-http-proxy
It allows you to send http or websocket requests through a proxy.
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create a basic proxy server in one line of code...
//
// This listens on port 8000 for incoming HTTP requests
// and proxies them to port 9000
httpProxy.createServer(9000, 'localhost').listen(8000);
//
// ...and a simple http server to show us our request back.
//
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2));
res.end();
}).listen(9000);
Source: link
Most web proxies don't support websockets yet. The best workaround is to use encryption by specifying wss:// (websocket secure protocol):
wss://ws.pusherapp.com:[port]/app/[key]

Stomp JS Basic Auth

I've implemented a server using Spring Stomp and now I'm trying to connect to my server using stomp-js rx-stomp.
What I find really awkward is that the JS implementation is not working, although I've managed to make it work using the Java stomp client.
Java client code(works):
WebSocketStompClient stompClient = new WebSocketStompClient(new SockJsClient(createTransportClient()));
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
final String URL = "http://localhost:" + port + "/ws";
// -< Headers used for authentication
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
String user = "user1", pass = "abcd1234";
headers.add("Authorization", "Basic " + getBasicAuthToken(user, pass));
StompSession stompSession = stompClient.connect(URL, headers, new StompSessionHandlerAdapter() {
}).get(10, TimeUnit.SECONDS);
JS client code(doesn't work):
connect: function() {
const stompConfig = {
connectHeaders: {
login: "user1",
passcode: "abcd1234",
Authorization: "Basic dXNlcjE6YWJjZDEyMzQ="
},
webSocketFactory: function() {
return new SockJS("http://localhost:8080/ws");
},
reconnectDelay: 60000
};
rxStomp = new RxStomp.RxStomp();
rxStomp.configure(stompConfig);
rxStomp.activate();
rxStomp.connected$.subscribe(res => {
if (res === 1) console.log('connected');
else console.log('not connected');
});
}
First of all, I find really awkward that I see a prompt asking my to enter a username and a password. If I enter the credentials there then the client connects successfully. So, I thought that I must be doing something wrong regarding the connect headers. As you can see, I've tried to add the Basic Auth token there, hoping that it would solve something. It doesn't.
The Java and the Javascript versions of the code, even though similar, differ in an important way. The Java version sets the Authorization header in the underlying HTTP connection of the Websocket. However, in the Javascript version, the HTTP connection is made, and then the Authorization header is passed as the STOMP CONNECT frame.
The browser Websocket API or SockJS does not allow setting custom headers to the underlying HTTP connection, which is used by the Java version of the code in the question. To support authentication, the brokers need to support receiving authentication parameters as part of the CONNECT frame (exposed as connectHeaders in the JS Stomp clients).
Spring does not, by default, support authentication parameters as part of the CONNECT frame. Please see https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket-stomp-authentication-token-based to support it.

Node.js hostname/IP doesnt match certificates altnames

I'm writing some node code to do a server side login to Facebook. I'm pretty close to having it completely functioning, but I am having troubles requesting the auth_code.
I'm thinking it might have something to do with the facebook app setting Site URL but nothing I've tried has worked.
I'm using node version 0.8.14.
Here is the code I do my request in:
options =
host: 'graph.facebook.com'
port: 443
path: "/oauth/access_token?" + qs.stringify(
client_id: app_id
redirect_uri: my_url
client_secret: app_secret
code: vars.code)
https.get(options, (face_res)->
console.log face_res
)
In the face_res I get a couple of objects like this:
authorizationError: 'Hostname/IP doesn\'t match certificate\'s altnames'
Any help is greatly appreciated
Ok funny thing is the error I was getting was completely irrelevant. As soon as I added a request.on("data", function(data){}) event listener I was able to see that I was receiving the auth_token as expected.

node.js making localhost client requests when system proxy is set

Here's the deal: I'm using node.js to connect to a local CouchDB instance.
The problem: the "response" event isn't firing. So I'm assuming it's not getting a connection.
var http = require('http'),
sys = require('sys'),
url = require('url');
var server = http.createServer(function (request, response) {
var client = http.createClient(5984, '127.0.0.1'); // couchdb
var request_db = client.request('GET', '/_all_dbs');
console.log('Created a request for _all_dbs');
request_db.addListener('response', function(response_db) {
console.log('Some response from CouchDB');
});
}).listen(8124);
console.log('Server running at http://127.0.0.1:8124/');
The only only output is:
Server running at http://127.0.0.1:8124/
Created a request for _all_dbs
I'm also guessing this might be a proxy problem. In Ubuntu 10.10, I have a system-wide proxy setting, but I tell it to ignore 127.0.0.1. Doing a curl on 127.0.0.1:5984/_all_dbs gives an error on "Error Code: 502 Proxy Error.".
First, try accessing 127.0.0.1:8124/_utils as #PartlyCloudy suggests.
Second, try using a couchdb lib such as cradle to make your life easier (and minimize mistakes)
npm install cradle
Remember to stop into #node.js and ask questions! Make sure to report back with your findings.
If you are using request to make the http request set proxy as empty string.. It will take preference over the system settings (you can do it only when the target ip is current machine ip)
var options = {
agent: ...,
url: ...,
method: ...,
proxy: ''
};
var req= request(options);
.
.
.

HTTP headers in Websockets client API

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.
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:password#example.com")
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
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
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).
HTTP Authorization header problem can be addressed with the following:
var ws = new WebSocket("ws://username:password#example.com/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://my_token#example.com/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.
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.
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 (,) !
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
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.
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
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
})
);
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);

Categories

Resources