Search code examples
node.jshttpsslhttpsexpress-4

How to configure express js 4 to serve some pages in http and others in https?


Is there any way to configure a node js application with express js 4 to serve some pages under http protocol and other, those which need more security, in https?

I describe my problem: I'm developing a shop online and I want to display certain pages, like the products list or the product detail views under http, and others which I think need more security, like login or the shopping cart views, under https protocol.

I have tried the express-force-ssl module, but it isn't working. The following code snippet is not from my app (which is too dirty) it is just an example which alos doesn't work for me:

var express = require('express');
var forceSSL = require('express-force-ssl');
var fs = require('fs');
var http = require('http');
var https = require('https');

var ssl_options = {
  key: fs.readFileSync('./server-private-key.pem'),
  cert: fs.readFileSync('./server-certificate.pem'),
  ca: fs.readFileSync('./server-certificate-signing-request.pem')
};

var app = express();

var server = http.createServer(app);
var secureServer = https.createServer(ssl_options, app);

app.use(forceSSL);

app.get('/', function (req, res, next) {
    res.send('<a href="/user/userA">Hello</a>')
});
app.get('/user/:name', function (req, res, next) {
    var user = req.params.name;
    res.send('<a href="/login">Hello ' + user + '</a>')
});
app.get('/login', forceSSL, function (req, res, next) {
    res.send('<a href="/">Hello</a><br/><a href="/logout">Goodbye</a>')
});
app.get('/logout', forceSSL, function (req, res, next) {
    res.send('<a href="/">Hello</a>')
});

secureServer.listen(443)
server.listen(8085)
console.log('server started');

The result is that when I launch the application, with url http://localhost:8085, the server automatically redirects it to https://localhost and serves all pages in https protocol.

What I want is to start on http://localhost:8085, navigate to http://localhost/user/userA, then from it go to https://localhost/login and, if click on "Hello" link, I would like to be redirected to http://localhost:8085.

Is there any missing code to get the behavior I want or even any other way to reach it without express-force-ssl module?


Solution

  • I have asked to the author of express-force-ssl module and he has told me that the redirect behavior works as expected. Here is the post.

    But diving a little more in its code I've created a custom plugin to solve my problem. Here is the code:

    var parseUrl = require('url').parse;
    var isSecure = function (req) {
        if (req.secure) {
            return true;
        } 
        else if (
            req.get('X-Forwarded-Proto') &&
            req.get('X-Forwarded-Proto').toLowerCase &&
            req.get('X-Forwarded-Proto').toLowerCase() === 'https') {
            return true;
        }
        return false;
    };
    exports = module.exports = function (req, res, next) {
        if (isSecure(req)) {
            if (req.method === "GET") {
                var httpPort = req.app.get('httpPort') || 80;
                var fullUrl = parseUrl(req.protocol + '://' + req.header('Host') + req.originalUrl);
                res.redirect('http://' + fullUrl.hostname + ':' + httpPort + req.originalUrl);
            } 
            else {
                next();
            }
        } 
        else {
            next();
        }
    };
    

    It's very similar to force-ssl file but here we manage the opposite action, i.e., here I redirect to http when a route is forced to it. So it's needed to add the function to every route we want to see under http protocol:

    var express = require('express');
    var forceSSL = require('express-force-ssl');
    var fs = require('fs');
    var http = require('http');
    var https = require('https');
    var useHttp = require('./useHttp');
    
    var ssl_options = {
      key: fs.readFileSync('./server-private-key.pem'),
      cert: fs.readFileSync('./server-certificate.pem')
    };
    
    var app = express();
    
    var server = http.createServer(app);
    var secureServer = https.createServer(ssl_options, app);
    
    app.get('/', useHttp, function (req, res, next) {
        res.send('<a href="/user/userA">Hello</a>')
    });
    app.get('/user/:name', useHttp, function (req, res, next) {
        var user = req.params.name;
        res.send('<a href="/login">Hello ' + user + '</a>')
    });
    app.get('/login', forceSSL, function (req, res, next) {
        res.send('<a href="/">Hello</a><br/><a href="/logout">Goodbye</a>')
    });
    app.get('/logout', forceSSL, function (req, res, next) {
        res.send('<a href="/">Hello</a>')
    });
    
    app.set('httpsPort', 9090);
    app.set('httpPort', 8085);
    
    secureServer.listen(9090)
    server.listen(8085)
    console.log('server started');
    

    As you can see I need now to specify in all routes which protocol use: useHttp for http or forceSSL for https.

    Although I'm not comfortable at all with this solution because I have to specify in all routes which kind of protocol I want. But at least it works. So I would be very pleased if someone finds any other solution, for isntance, adding in middleware layer a function to manage all http requests and just redirect to https when it is specified with forceSSL. By the moment, this works.