0

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?

christiansr85
  • 867
  • 19
  • 40
  • I haven't used express-force-ssl before, but I looked into it a while ago for a project. You might want to try removing the app.use(forceSSL) when you're using mixed https and http routes (as well as non standard ports). – Adam Wysocki Jul 02 '15 at 17:12
  • I've tried it, @Adam, commenting `app.use(forceSSL);` instruction and changing the port of the secure server (`app.set('httpsPort', 9090);` and `secureServer.listen(9090)`) and now `http` pages are working well, but when I want to go to a secure page, server redirects to `https://localhost/login` instead of `https://localhost:9090/login`. Have I to configure any property of `forceSSL` module? – christiansr85 Jul 02 '15 at 17:23
  • I don't know enough about express-force-ssl to know if there are additional options. Have you tried to reach out to the package creator? https://www.npmjs.com/~complexcarb Might be worth a shot. – Adam Wysocki Jul 02 '15 at 17:56

1 Answers1

0

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.

christiansr85
  • 867
  • 19
  • 40