2

I have a NodeJS HTTP (non S) server running on Heroku. I have SSL configured, and it accepts requests to HTTPS. The reason I am using a vanilla HTTP server is because of the following:

SSL termination occurs at Heroku's load balancers; they send your app plain (non-SSL) traffic, so your app should create a non-HTTPS server." 

Unfortunately, my app still responds to plain-old HTTP requests. I want to force a redirect or something from HTTP to HTTPS. I could do this with some middleware:

/* At the top, with other redirect methods before other routes */
app.get('*',function(req,res,next){
  if(req.headers['x-forwarded-proto']!='https')
    res.redirect('https://mypreferreddomain.com'+req.url)
  else
    next() /* Continue to other routes if we're not redirecting */
})

But is this a good solution? How do POST requests work? If I post to HTTP, should it be allowed?

The other final approach I was thinking, was to use Nginx and stick a redirect in there from HTTP to HTTPS. Unfortunately Heroku doesn't allow Nginx configs.

Community
  • 1
  • 1
Dominic Bou-Samra
  • 14,799
  • 26
  • 100
  • 156
  • Most likely your issue is lowercase header arg name `x-forwarded-proto` which must not match the key in the array, because header created by heroku starts with capital letters: `X-Forwarded-Proto`. You could fix this by changing the header name, or if you'd use `reg.get('x-forwarded-proto')` you would get the result because it is case-insensitive. – Zilvinas Mar 02 '17 at 19:10

3 Answers3

1

Better to use app.use(function(req, res, next{ /* ... */ }); to catch other HTTP methods.

mscdex
  • 104,356
  • 15
  • 192
  • 153
1

So assuming you put this at the very head of your middleware chain, before the router itself, this should be very performant.

But let's take it up a notch. Say you deployed this server in multiple environments, not just Heroku, then you would be hard pressed to avoid doing some conditional inclusion of the middleware or constant conditional branching within it should you wish to harmonize the behavior for multiple environments.

You can just skip over all that if you write a specific server for Heroku, something along the lines:

var app = require('./app').createApp();
var server = require('http').createServer(function (req, res) {
    if (!secure(req)) {
        // Redirect
        res.statusCode = 302;
        res.setHeader('Location', pathname);
        res.setHeader('Content-Length', '0');
        res.end();
    } else {
        app.handle(req, res);
    }
});
server.listen(80);

Also, try to refrain from manually constructing URLs. You should really try to use the URL library, specifically the url.parse() and url.resolve() functions, since parsing and construction URLs is non-trivial (think about implicit/explicit trailing slashes, hashes, queries and URL encodings).

Here's a small preview:

var url = require('url');

url.parse('http://www.foo.com?bar=baz#quux')
{ protocol: 'http:',
  slashes: true,
  auth: null,
  host: 'www.foo.com',
  port: null,
  hostname: 'www.foo.com',
  hash: '#quux',
  search: '?bar=baz',
  query: 'bar=baz',
  pathname: '/',
  path: '/?bar=baz',
  href: 'http://www.foo.com/?bar=baz#quux' }
Filip Dupanović
  • 32,650
  • 13
  • 84
  • 114
0

In case anyone is still looking for a solution, create a function within your NodeJs and Express JS file.

var enforceSsl = function (req, res, next) {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    return res.redirect(['https://', req.get('Host'), req.url].join(''));
  }
  return next();
};

Then apply the middleware using

app.use(enforceSsl);
Moshood Aminu
  • 271
  • 1
  • 5