0

Let me start by saying that I know that what I'm trying to do, is a bit unorthodox, but I was wondering if any of your brilliant minds could help me workaround this.

We have an old system, where the HTML is hosted on a remote server which is connecting to a local server with socket.io@2.2.0 (I know it's old) using http (because the server needs to be hosted on the local machine and self-signed SSLs are not working very well).

What we used to do is to disable "Block insecure private network requests" in the chrome flags chrome://flags/#block-insecure-private-network-requests, but this seems to have gone away now which left us getting a CORS error Access to XMLHttpRequest at 'http://localhost/socket.io/?EIO=3&transport=polling&t=OLTDEpr' from origin 'http://remote.com' has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `local`.

enter image description here

I have tried stuff like the bits suggested in this post Socket.io + Node.js Cross-Origin Request Blocked but none of them is working.

I stripped down the system to the absolute basics so I can try to find a workaroud, but I haven't manage to do so. At the end of the post I have copied a link to the code of both these two servers.

LOCAL SERVER

const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require("socket.io")(http);
io.origins('*:*');


app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", '*');
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});

io.on("connection", function(socket){
    console.log("A new connected, with socketid " + socket.id);
});

http.listen(80, function(){
    console.log("start");
});

REMOTE SERVER

const express = require('express');
const app = express();
const http = require('http').Server(app);

app.use(express.static('public'));
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');

app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", '*');
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});

app.get('/*', function(req, res){
    res.render('home');
});

http.listen(80, function(){
    console.log("Server started");
});

REMOTE HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./js/site.js"></script>
</head>
<body>
    
    <script>
        Test.Init("http://localhost");
    </script>
</body>
</html>

REMOTE JS


/*!
 * Socket.IO v2.1.0
 * (c) 2014-2018 Guillermo Rauch
 * Released under the MIT License.
 */
/// I HAVE A COPY OF THAT Socket.IO CLIENT CODE IN HERE. ///

const Test = function(){

    const Init = (url) => {
        const socket = io(url);
        socket.on('connect', function(){
            console.log("Successfully connected!");
        });
    }

    return {
        Init
    }
}();

Here's the zipped code too if you want to experiment. https://1drv.ms/u/s!AkpIm5Ify5v9pqh8Ox6-32GoT2Hecw?e=l6lHeC

Many thanks, and happy new year to all :)

Heiko Theißen
  • 12,807
  • 2
  • 7
  • 31
Nick
  • 221
  • 2
  • 12

1 Answers1

1

If your Chrome browser already enforces the newish "private network access" protocol, it will generate preflight requests, to which your local server must respond with the header

Access-Control-Allow-Private-Network: true

This could be achieved with the following middleware before the CORS handling middleware you already have:

app.options(function(req, res, next) {
  if (req.get("Origin") === "https://<your remote server>" &&
      req.get("Access-Control-Request-Private-Network"))
    res.set("Access-Control-Allow-Private-Network", "true");
  next();
});
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", '*');
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

Note private network access is only allowed if the remote server is on HTTPS, and this requires the local server to be on HTTPS as well (to avoid "mixed content"). To enable HTTPS for a local IP, you can generate a server certificate for this IP address with the following steps:

Step 1. Generate a self-signed CA certificate, which must be uploaded to the browser as "trusted root certificate":

openssl req -new -subj /CN=YourCAName -x509 -key cakey.pem -out cacert.pem

(You must specify a passphrase to protect the CA private key.)

Step 2. Generate a server certificate for your local server and have it signed by your CA certificate:

echo subjectAltName = IP:xx.xx.xx.xx > IP.ext
openssl req -new -subj /CN=YourSubjectName \
| openssl x509 -req -CA cacert.pem -CAkey cakey.pem -extfile IP.ext

where xx.xx.xx.xx is your local IP address. (You must specify a passphrase for the server private key and repeat the passphrase for the CA private key.)

Step 3. Use the server certificate and the server private key (together with its passphrase) in the TLS options of your local Node.js HTTPS server.

See also How to create .pem files for https web server.

Heiko Theißen
  • 12,807
  • 2
  • 7
  • 31
  • Hi Heiko, many thanks for the super quick reply! My browser does support the edge://flags/#private-network-access-respect-preflight-results, I just checked. I have added your code (an replaced the "" with my server) between the io.origins('*:*'); and app.use(function(req, res, next) but I'm still getting the error. I have also tried to "Empty cache and hard refresh". Did I place it in the wrong place? – Nick Dec 29 '22 at 11:12
  • I already had the above in the code, but I realised that you were using https in the "Origin", so I converted the remote server to HTTPS and tried that and it worked! Many thanks! – Nick Dec 29 '22 at 12:50
  • Hm, didn't work for me with the http:// to be honest, and now I noticed another issue. As long as I use http://localhost in the javascript in my html, it all works fine. But the moment I switch to my local IP, it errors saying "Mixed Content:....This request has been blocked; the content must be served over HTTPS.". I then added the "res.header('Content-Security-Policy', 'upgrade-insecure-requests');" in the remote server, and now it's saying "GET https://LOCALIP/socket.io/?EIO=3&transport=polling&t=OLUPwjx net::ERR_CONNECTION_REFUSED". I assume it converts it to https internally? – Nick Dec 29 '22 at 15:03
  • If my remote server is http, I'm getting this error (both for localhost and to localIP). Access to XMLHttpRequest at 'http://LOCALIP/socket.io/?EIO=3&transport=polling&t=OLUd-aN' from origin 'http://remote.com' has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `private`. – Nick Dec 29 '22 at 16:06
  • I guess so, but that's the issue. When when I move the remote server to https, it works fine with http://localhost. But when I use the http://local_IP, it compains about mixed content (as remote is https and local http). When I upgrade the request to https, it then breaks as there is no https://local_IP. :/ So, do you think that there is a way to have both the remote and the local set up as HTTP? – Nick Dec 29 '22 at 16:25
  • many thanks for all the replies. The issue though is that you can't have https on local IPs, unless if there is away I'm not aware off. – Nick Dec 29 '22 at 16:40
  • See my augmented answer. – Heiko Theißen Dec 29 '22 at 17:15
  • Hi Heiko, many thanks for your answer! I'm a little bit confused though. So, in step 1, you generate a certificate (in the local machine?) and then you manually import it in the browser? I assume YourCAName can be anything? And then on step 2, you generate a new certificate (in the remote machine?), using the IP of the local? What's the case if you have more than one local machines/IPs connecting to that server (cause we have about 5 with Dynamic IPs). also, I assume the same can be done with this module, right? https://www.npmjs.com/package/openssl-nodejs – Nick Dec 30 '22 at 05:52
  • @Nick, no both certificates are on the local machine. Perhaps it can even be _one_ self-signed certificate, but I could not manage to one with an IP address as subjectAlternativeName. At any rate, this merits a separate question (or there is already one on StackOverflow). – Heiko Theißen Dec 30 '22 at 08:33
  • Hi Heiko, Happy New Year! Many thanks for the suggestion. Some priorities changed this year so I will have to drop it for now. If I have to pick this up, would you be available for some freelancing if required? Feel free to add me on LinkedIn if you're interested https://uk.linkedin.com/in/nickmoutafis – Nick Jan 04 '23 at 12:24