15

I have an app on Heroku that was created using create-react-app. Just today, I got an SSL cert using Heroku's automated(-ish) SSL cert process ExpeditedSSL, and the documentation then suggests rerouting all http requests to https.

I have a server.js file and express I use just to attempt to run middleware and then serve my React app.

I know the SSL cert is working as if I go to https://myapp.com I see my Heroku app, but when I go to http://myapp.com it is not redirected to the https version of my Heroku app.

I have tried many, many solutions today from StackOverflow, Google, and otherwise and none of the the solutions have worked for me. I don't get any errors, either. It just doesn't work.

Attempt using https library:

const https = require("https");
const express = require('express');
const app = express();

app.use(express.static(path.join(__dirname, 'build')));

app.get('/', function (req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

https.createServer(app).listen(3000);

Another attempt using heroku-ssl-redirect:

var sslRedirect = require('heroku-ssl-redirect');
var express = require('express');
var app = express();

// enable ssl redirect
app.use(sslRedirect(['production'], 301));

app.use(express.static(path.join(__dirname, 'build')));

app.get('*', (req, res, next) => {
  if (req.headers['x-forwarded-proto'] != 'https'){
    res.redirect('https://' + req.hostname + req.url);
  } else {
    next();
  }
});

app.get('/', function (req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(process.env.PORT || 3000);

Attempt using x-forward-proto:

const express = require('express');
const env = process.env.NODE_ENV || 'development';
const bodyParser = require('body-parser');
const path = require('path');
const app = express();

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

if (env === 'production') {
  app.use(forceSsl);
}

app.use(express.static(path.join(__dirname, 'build')));

app.get('/', function (req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(process.env.PORT || 8080);

I've also attempted a number of random node installs from various blogs and SO posts, and nothing has worked. Without any errors I am having a hard time figuring out why this doesn't work.

smkarber
  • 577
  • 5
  • 18

5 Answers5

18

Try adding the following to the header of your index.html file

<script>
var host = "www.URL.com" || "URL.com";
if ((host == window.location.host) && (window.location.protocol != "https:")){
window.location.protocol = "https";
}
</script>

I was dealing with a similar issue and this allowed me to resolve it. Placing it in the header ensures that the script will run before any of your bundled JavaScript, but I suppose it would work above your bundle.js file

misterfoxy
  • 196
  • 3
  • 1
    Strangely, I needed to do an `else if()` with each `host` string being declared independently to get this to work, which makes no sense, but nevertheless was the case. e.g. `let longUrl = 'www.url.com'` `let shortUrl = 'url.com'`... `if (longUrl === ....)` ... `else if (shortUrl === .... )`. – Reece Daniels Aug 16 '19 at 00:37
  • @misterfoxy just want to say personal "Thank you" as your answer saved me a lot of time :) – jphawk Jul 16 '20 at 12:38
  • 4
    I'm just curious if handling this at the application layer has any consequences? Indexed differently? SEO? – Peege151 Aug 19 '20 at 19:23
  • @RubenMurray & Misterfoxy you guys saved me. I also had to do the two else if as shown above – AndrewLeonardi Sep 09 '22 at 00:29
  • The `else if` change is a requirement because the `var` is declared as an `||`. `www.url.com` == `url.com || 'www.url.com'` is `false`, but `url.com` == `url.com || 'www.url.com'` is `true` My comment 3 years ago missed this. I've submitted an edit to the existing solution which should be clearer, though the more I read it I'm not sure exactly why the `url` is required at all. Why not just always forward to `https` if the protocol isn't set. The url should always be as expected given it's your own site. ALSO: There is an answer below which is apparently better. – Reece Daniels Sep 18 '22 at 12:32
10

UPDATED and SECURE solution below:

Since you are deploying your react app via Heroku using create-react-app and it's buidlpack (recommended, if you are not: create-react-app-buildpack). So as the official docs says:

The web server may be configured via the static buildpack.

The config file static.json should be committed at the root of the repo. It will not be recognized, if this file in a sub-directory

The default static.json, if it does not exist in the repo, is:

{
  "root": "build/",
  "routes": {
    "/**": "index.html"
  }
}

HTTPS-only

Enforce secure connections by automatically redirecting insecure requests to https://, in static.json:

{
  "root": "build/",
  "routes": {
    "/**": "index.html"
  },
  "https_only": true
}

The method by @misterfoxy works but is not the right way!

Dharman
  • 30,962
  • 25
  • 85
  • 135
Frederiko Ribeiro
  • 1,844
  • 1
  • 18
  • 30
0

Try using express-https-redirect.

Install with:

npm install express-https-redirect --save

Then you should be able to do something like:

const express = require('express');
const httpsRedirect = require('express-https-redirect');
const app = express();
app.use('/', httpsRedirect());
app.set('port', process.env.PORT || 3000);
app.use(express.static(path.join(__dirname, 'build')));

app.get('/', function (req, res) {
    res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(app.get('port'), function () {
        console.log('Express server listening on port ' + app.get('port'));
});
Yoni Rabinovitch
  • 5,171
  • 1
  • 23
  • 34
  • I will try this as soon as I get home. Thanks. – smkarber Feb 20 '18 at 14:40
  • 1
    This also seems to fail. If I type in the url sans `https` or using `http`, it just goes to the non-secure url. With `https` in the url it works as one would hope, but I cannot get it to redirect from http to https. – smkarber Feb 26 '18 at 02:17
0

Try adding this to your index.html file Place it in the header, so it would run before other scripts

<script>
const domain1 = "www.example.com";
const domain2 = "example.com";
const host = window.location.host;
if ((domain === host || domain2 === host) && (window.location.protocol !== "https:")){
  window.location.href = "https://www.example.com";
}
</script>
David Buck
  • 3,752
  • 35
  • 31
  • 35
iMyke
  • 573
  • 1
  • 7
  • 10
0

This seem to work for me React on Heroku

Buildpack https://github.com/mars/create-react-app-buildpack

root/static.json

{
  "root": "build/",
  "routes": {
    "/**": "index.html"
  },
  "https_only": true
}

https://youtu.be/LAjj_kzqbjw

atazmin
  • 4,757
  • 1
  • 32
  • 23