15

I have a simple Express.js instance that's serving up static assets for a single page Angular app. I set up some middleware on the Express config so that index.html is returned for all routes and Angular can load from there.

More recently, I set up SSL on Heroku and I want to make sure that all traffic that comes in from HTTP is redirected to HTTPS. I tried to combine the suggested solution from this post with what I have now, but end up in an endless redirect loop.

In short, I need all traffic to be redirected from HTTP to HTTPS and the index.html file to be sent for all requests. What am I doing wrong here?

var gzippo = require('gzippo');
var express = require('express');
var morgan = require('morgan');
var app = express();

// set environment variables
var env = app.get('env') || 'development';

app.use(morgan('dev'));

// serve static assets
app.use(gzippo.staticGzip("" + __dirname + "/dist"));
app.use("/js", express.static(__dirname + "/dist/scripts"));
app.use("/fonts", express.static(__dirname + "/fonts"));
app.use("/img", express.static(__dirname + "/dist/assets/images"));
app.use("/css", express.static(__dirname + "/dist/styles"));


// Redirect all HTTP traffic to HTTPS
function ensureSecure(req, res, next){
  if(req.secure){
    // OK, continue
    return next();
  };
  res.redirect('https://'+req.hostname+req.url); // handle port numbers if you need non defaults
};


// Always send index.html
function sendIndex(req, res, next) {
  res.sendfile('index.html', { root: __dirname + "/dist/"});
}


// Handle environments
if (env == 'production') {
  app.all('*', ensureSecure);
}

app.all('/*', sendIndex);

// Start server
app.listen(process.env.PORT || 5000);
Community
  • 1
  • 1
Will Hitchcock
  • 4,648
  • 3
  • 22
  • 32

1 Answers1

29

Heroku terminates SSL connections at the load balancer level, so req.secure will never be true, because your connection to heroku's load balancer is not using SSL, thus creating an infinite redirect loop.

You have to check the X-Forwarded-Proto header instead:

if(req.headers["x-forwarded-proto"] === "https"){
  // OK, continue
  return next();
};
res.redirect('https://'+req.hostname+req.url);

Edit: you can also set app.enable("trust proxy") to have express check the headers automatically. See http://expressjs.com/guide/behind-proxies.html

Philippe
  • 1,169
  • 12
  • 14
  • Awesome, this seems to work for me. The only other problem I have here is that it doesn't apply the redirect on the root, only on other routes. I tried changing ```app.all``` to ```app.use(ensureSecure);``` with no luck. Any ideas on that? – Will Hitchcock Oct 05 '15 at 16:28
  • @WillHitchcock Yes, I was going to suggest using app.use instead of app.all, but not sure what the issue is if this doesn't work. Try to add some more logs to track what happens in that case. – Philippe Oct 05 '15 at 16:40
  • 2
    I think I got it figured out. I just needed to put the redirect up before the ```app.use(gzippo...``` line. Working now! – Will Hitchcock Oct 05 '15 at 16:56
  • 1
    Worked perfect for me. Thanks a lot. :) – isaacsan 123 Sep 14 '21 at 22:34