Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express.js force HTTPS/SSL redirect error: Too many redirects

So the stack is this: Express & Angular deployed to Heroku. I'm trying to serve the app forced over HTTPS by using a simple redirect using the code highlighted below. However when I open up the URL in the browser, I get a 'too many redirects' error.

When I type http://[myurl].com, I can see the URL in the browser changing to https://[myurl].com but nothing shows up at that address. It just shows 'Too many redirects'. This proves that it successfully redirects but I feel like Angular is messing it up.

When I remove the redirecting code below and access HTTPS or the HTTP address manually in the browser, it works fine.

//HTTPS redirect middleware
function ensureSecure(req, res, next) {
    console.log(req.protocol + process.env.PORT + '' + req.hostname + req.url);
    if(req.secure || req.hostname=='localhost'){
        //Secure request, continue to next middleware
        next();
    }else{
        res.redirect('https://' + req.hostname + req.url);
        console.log(req.protocol + process.env.PORT + '' + req.hostname + req.url);
    }
}
//Parse the body of the request as a JSON object, part of the middleware stack (https://www.npmjs.com/package/body-parser#bodyparserjsonoptions)
app.use(bodyParser.json());
//Serve static Angular JS assets from distribution, part of the middleware stack, but only through HTTPS
app.all('*', ensureSecure);
app.use('/', express.static('dist'));
//Import routes
app.use('/api', [router_getToken, router_invokeBhApi]);
//Setup port for access
app.listen(process.env.PORT || 3000, function () {
    console.log(`The server is running on port ${process.env.PORT || 3000}!`);
});

This is a sample of the heroku logs when you visit http://[myurl].com (I masked the URL):

2016-11-29T21:50:34.363391+00:00 app[web.1]: 0|app      | http37436[something].com/
2016-11-29T21:50:34.363468+00:00 app[web.1]: 0|app      | http37436[something].com/
2016-11-29T21:50:34.402022+00:00 app[web.1]: 0|app      | http37436[something].com/
2016-11-29T21:50:34.402091+00:00 app[web.1]: 0|app      | http37436[something].com/
2016-11-29T21:50:34.436006+00:00 app[web.1]: 0|app      | http37436[something].com/
2016-11-29T21:50:34.437454+00:00 app[web.1]: 0|app      | http37436[something].com/
2016-11-29T21:50:34.479580+00:00 app[web.1]: 0|app      | http37436[something].com/

The browser (Chrome latest) shows these requests in the Network tab over & over & over again:
'Request URL:https://[myurl].com/
Request Method:GET
Status Code:302 Found'

Notice how Heroku (the console.log in the express.js code) shows that I'm making HTTP requests but my browser is saying I'm making HTTPS requests. So confused!

EDIT: I tried this as well

//HTTPS redirect middleware
function ensureSecure(req, res, next) {
    console.log(req.protocol + process.env.PORT + '' + req.hostname + req.url);
    if (req.secure || req.hostname == 'localhost') {
        //Serve Angular App
        express.static('dist');
    } else {
        //res.redirect('https://' + req.hostname + ':' + process.env.PORT + req.url);
        res.redirect('https://[myurl].com/');
    }
}
//Parse the body of the request as a JSON object, part of the middleware stack (https://www.npmjs.com/package/body-parser#bodyparserjsonoptions)
app.use(bodyParser.json());
//Serve static Angular JS assets from distribution, part of the middleware stack, but only through HTTPS
app.use('/', ensureSecure);
//Import routes
app.use('/api', [router_getToken, router_invokeBhApi]);
//Setup port for access
app.listen(process.env.PORT || 3000, function () {
    console.log(`The server is running on port ${process.env.PORT || 3000}!`);
});
like image 465
ykadaru Avatar asked Nov 29 '16 22:11

ykadaru


2 Answers

Found the solution!

Context: Heroku stores the origin protocol in a header variable called 'X-Forwarded-Proto'. HTTP Routing in Heroku You need to check this variable and not the protocol variable tied to the 'req' object in Express. (that is, don't check req.protocol, check req.get('X-Forwarded-Proto') instead)

Code:

//HTTPS redirect middleware
function ensureSecure(req, res, next) {
    //Heroku stores the origin protocol in a header variable. The app itself is isolated within the dyno and all request objects have an HTTP protocol.
    if (req.get('X-Forwarded-Proto')=='https' || req.hostname == 'localhost') {
        //Serve Angular App by passing control to the next middleware
        next();
    } else if(req.get('X-Forwarded-Proto')!='https' && req.get('X-Forwarded-Port')!='443'){
        //Redirect if not HTTP with original request URL
        res.redirect('https://' + req.hostname + req.url);
    }
}
like image 168
ykadaru Avatar answered Nov 01 '22 11:11

ykadaru


You can pass the option

{ trustProtoHeader: true }

to the HTTPS() method; for example:

const enforce = require('express-sslify');
app.use(enforce.HTTPS({ trustProtoHeader: true }));

That fixed it for me

like image 3
STh Avatar answered Nov 01 '22 10:11

STh