I'm trying to redirect all http requests to https for a node.js express server deployed on Heroku.

To that extent, I'm testing the x-forwarded-proto header of the request like this:

app.use(function(req, res, next) {
  if (!req.secure && req.get("x-forwarded-proto") !== "https") {
    res.redirect("https://" + req.headers.host + req.url);
  } else {

However, both Heroku and Express docs seem to point out that the X-Forwarded-Proto header is not trusted "for security reasons":

Express docs:

X-Forwarded-* headers are easily spoofed and the detected IP addresses are unreliable.

Heroku docs:

The X-Forwarded-For, X-Forwarded-By, X-Forwarded-Proto, and X-Forwarded-Host headers are not trusted for security reasons, because it is not possible to know the order in which already existing fields were added.

Is my understanding correct: can it be altered by an attacker so that we might believe our app is accessed via https when in fact it is using http?

If so, is there any other (secure) way to verify the protocol used to access an application deployed on Heroku?

Note: I have also asked this question on stackoverflow without much success though. Higher hopes here...

  • 1
    What is stopping you from returning HTTP 301, together with a Strict Transport Security header? – MechMK1 May 27 '19 at 9:10
  • Hmm, good point. I added HSTS, but it still would not redirect - this snippet is from Mozilla Observatory: pasteboard.co/IgCn4wJ.png. However, I did not return the 301 because i go back to my initial problem: when do I return it? What condition should I check to verify the request came through plain http? Does this make sense? :) – Ioanna May 27 '19 at 9:44
  • req.connection.encrypted should be available, although this is off-topic here and better asked on Stack Overflow. As for "When to return it?" - Whenever you get an HTTP request, return 301 to the same ressource, but via HTTPS. – MechMK1 May 27 '19 at 9:52
  • Yes, the "when" question was about the if condition :), how to verify that the request came via http, except using the x-forwarded-proto. When I deploy to heroku, req.connection.encrypted is undefined, req.secure is always false (even when called via https) and using req.protocol goes back to the x-forwarded-proto... Ended up using another package... (I did ask on stackoverflow, but maybe I just don't ask the right question...) – Ioanna May 27 '19 at 10:38

X-Forwarded-* headers are used by proxy strategies (WAF/Load Balancer/DLP HTTP Gw/etc.) and yes anyone can send any header without custom development, have a look following command (bash).

Example custom header command: curl http://test.com -H 'X-Forwarded-For:'

This why Heroku does not accept X-Forwarded-* headers from WAN, this is the case. You can not send X-Forwarded-* headers with JS but Heroku will proxy your request and give you X-Forwarded headers (to your backend).

You can verify my answer at https://help.heroku.com/J2R1S4T8/can-heroku-force-an-application-to-use-ssl-tls.

Under the hood, Heroku router (over)writes the X-Forwarded-Proto and the X-Forwarded-Port request headers. The app checks X-Forwarded-Proto and responds with a redirect response when it is not https but http.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.