Securing (ssl) a nodejs/express web service and calling it cross-domain with $.ajax()

I've set up a nodejs app like this:

var express =   require('./../../../Library/node_modules/express'); 
var https = require('https');
var app = express();

httpsOptions = {
  key: fs.readFileSync('privatekey.pem'), 
  cert: fs.readFileSync('certificate.pem')  // SELF-SIGNED CERTIFICATE
}

var server = https.createServer(httpsOptions);
app.get('/myservice', function(req, res) {
...
}
server.listen(8443);

I have opened the 8443 port in my server for inbound requests. From a browser, if I open https://mydomain/myservice:8443 I get the untrusted connection warning from the browser, which seems logical.

Then from a test.html that I run from my local computer (to test the cross-domain issue), I do something like this:

function testService(){
   var data =  { some JSON };

    $.ajax({
            url: 'https://myserver:8443/myservice',
            dataType: "jsonp",
            data: data,   
            jsonpCallback: "_mycallback",
            cache: false,
            timeout: 5000,
            success: function(data) {
                alert(data);
            },
            error: function(jqXHR, textStatus, errorThrown) {
                alert('Error: ' + textStatus + " " + errorThrown);
            }
        });
}

My problem is that this request times out, and I don't think it even reaches the service.

  1. Any idea why?

  2. Whenever I make this request reach the server, hopefully thanks to your kind responses, what will happen with the browser warning for the untrusted certificate? Will that stop $.ajax() from silently calling the server and receiving the response?

The reason that your clients' JSONP request times out could be practically anything. Because of the way JSONP works, you can only ever know whether the request fails or succeeds, and when it fails it will always be because of a timeout. That said, its pretty much guaranteed to fail if you haven't saved the servers self-signed cert on the client. To do so, make sure that you tell your browser to always trust the servers' certificate. In Firefox you can also go Preferences->Encryption->View Certificates->Your Certificates->Import... to add the certificate to Firefox. Other browsers should have a similar interface.

To solve a potential cross domain issue, try adding the following to your app.get('/myservice'):

res.header("Access-Control-Allow-Origin:", "*");
res.header("Access-Control-Allow-Methods:", "GET");

Additionally, different browsers handle these things differently. In my experience Firefox is sometimes more lenient than Chrome, but I would definitely test in both.

To test the HTTPS issue, first I would try just setting up a regular expressjs server (no encryption) and not using https:// in your request. If the request then succeeds you know that the problem is the SSL. If so, make sure that when your browser gives a security warning you enable any options allowing you to permanently add that site to your trusted hosts.

Also, I believe that this line:

var server = https.createServer(httpsOptions);

should be:

var server = https.createServer(httpsOptions, app);

(From: http://expressjs.com/api.html#app.listen)

You may also want to add the following code below var server = https.createServer(httpsOptions); for debugging (so that you can easily see if your server receives the request):

app.get('*', function(req, res, next) {
    // You *should* also be able to add the response headers here, although I haven't tried.
    console.log('Request received', req, res);
    next();
})

Hopefully that helps!