ember-fastboot / fastboot-app-server

A production-ready app server for running Ember FastBoot apps
140 stars 72 forks source link

Common use case: force HTTPS via beforeMiddleware #36

Open mike-north opened 7 years ago

mike-north commented 7 years ago

Volunteering some example code, as a potential candidate for README. This uses the relatively new beforeMiddleware hook to force http traffic to https, with a provision for the EBS health check feature (which seems to come over HTTP unless HTTP is totally disabled for the env).


const enforceHTTPS = (req, res, next) => {
  // Header indicates edge server received request over HTTPS
  if (req.headers['x-forwarded-proto'] === 'https'){
    return next();
  } else {
    // Did not come over HTTPS. Fix that!
    return res.redirect(301, join(`https://${req.hostname}${req.url}`));
  }
};

let server = new FastBootAppServer({
  ...
  beforeMiddleware(app) {
    app.use((req, res, next) => {
      if (process.env.DISABLE_FORCE_HTTPS || // Ability to disable force HTTPS via env
          req.headers['user-agent'].indexOf('HealthChecker') >= 0) { // EBS health over HTTP
        return next(); // Proceed as planned (http or https -- whatever was asked for)
      } else {
        return enforceHTTPS(req, res, next); // Middleware to force all other HTTP --> HTTPS
      }
    });
  }
});
marcoow commented 7 years ago

Isn't forcing HTTPS in the app server something that is more a leftover from Rails' early days when everyone used HTTPS only for some (sensitive) endpoints while nowadays everyone is (should be) using HTTPS only all of the time in which case nginx would just redirect all HTTP requests to HTTPS so that no requests that are not HTTPS would ever reach the app server?

I'm not saying this is not a problem people have, I'm only saying the answer to them should not be "here is how you enforce HTTPS in Express" but "here's how you fix your nginx config".

mike-north commented 7 years ago

100% agree with what @marcoow said, however it's not straight forward to get into nginx config (or even apache rewrite rules) when hosting on a Paas like heroku or elastic beanstalk. Related: https://github.com/heroku/heroku-buildpack-emberjs/issues/8

jakeleboeuf commented 7 years ago

Any updates on this? I'm currently using heroku-buildpack-emberjs and need to find a solution to force HTTPS.

ghost commented 7 years ago

@jakeleboeuf I'm in the same situation. Have you found a solution? The only thing I can think of is downloading the buildback, manually using @mike-north 's solution, and then reuploading the custom buildpack to an s3 bucket to use with heroku. Very not ideal but I'm not sure there's another alternative yet?

FutoRicky commented 6 years ago

@jakeleboeuf @konnorbeard Found any other solutions?

musaffa commented 6 years ago

There's a solution for Heroku as discussed in this thread: https://github.com/heroku/heroku-buildpack-emberjs/issues/8

devdemi commented 6 years ago

what if I do it on nginx? I have nginx with next config:

server {
    listen       80 default_server;
    server_name  mysite.me;
    root   /usr/share/nginx/html;

    gzip on;
    gzip_comp_level 5;
    gzip_disable "msie6";
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;

    location /health_check {
        access_log off;
        return  200;
    }

    location / {
        if ($http_x_forwarded_proto != 'https') {
            return 301 https://$server_name$request_uri;
        }
        resolver 8.8.8.8;
        proxy_pass https://324234234234.eu-central-1.elb.amazonaws.com:3443;
        proxy_redirect     off;
        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
server {
    listen       80;
    server_name  www.mysite.me;
    return       301 https://mysite.me$request_uri;
}
server {
    listen       80;
    server_name  blog.mysite.me;
    return       301 https://mysite.me/blog$request_uri;
}

So Ngnix does redirect from http to https and proxy request to FastBootAppServer. But when FastBoot tries to redirect It adds port 3443 to host like it ignores request's header Host $host. How transitionTo works on FastBoot? How does FastBoot get host for redirecting? Please advise how to figure out the problem.

ArtOfSettling commented 5 years ago

To slightly expand upon what @mike-north wrote, we can have a slightly modified version that prefers HTTPS and naked domains.

For anyone like me having many headaches using Apache ProxyPass, if you decide to forgo Apache altogether.


const enforceHTTPS = (req, res, next) => {
  // Header indicates edge server received request over HTTPS
  if (req.headers['x-forwarded-proto'] === 'https') {
    return next();
  } else {
    return res.redirect(301, `https://${req.hostname}${req.url}`);
  }
};

const enforceNaked = (req, res, next) => {
  if(/^www\./.test(req.headers.host)) {
    res.redirect(req.protocol + '://' + req.headers.host.replace(/^www\./,'') + req.url,301);
  } else {
    next();
  }
}

let server = new FastBootAppServer({
  notifier: notifier,
  downloader: downloader,
  gzip: true,
  beforeMiddleware(app) {
    app.use((req, res, next) => {
      if (req.headers['user-agent'].indexOf('HealthChecker') >= 0) {
        return next(); // Proceed as planned (http or https -- whatever was asked for)
      } else {
        return enforceHTTPS(req, res, next); // Middleware to force all other HTTP --> HTTPS
      }
    });

    app.use((req, res, next) => {
      return enforceNaked(req, res, next);
    });
  }
});```