How to force SSL / https in Express.js

node.jsRedirectSslHttpsExpress

node.js Problem Overview


I am trying to create a middleware for Express.js to redirect all non-secure (port 80) traffic to the secured SSL port (443). Unfortunately there is no information in an Express.js request that lets you determine if the request comes over http or https.

One solution would be to redirect every request but this is not an option for me.

Notes:

  1. There is no possibility to handle it with Apache or something else. It has to be done in node.

  2. Only one server can be fired up in the application.

How would you solve that?

node.js Solutions


Solution 1 - node.js

Just in case you're hosting on Heroku and just want to redirect to HTTPS regardless of port, here's the middleware solution we're using.

It doesn't bother to redirect if you're developing locally.

function requireHTTPS(req, res, next) {
  // The 'x-forwarded-proto' check is for Heroku
  if (!req.secure && req.get('x-forwarded-proto') !== 'https' && process.env.NODE_ENV !== "development") {
    return res.redirect('https://' + req.get('host') + req.url);
  }
  next();
}

You can use it with Express (2.x and 4.x) like so:

app.use(requireHTTPS);

Solution 2 - node.js

Although the question looks a year old, I would like to answer as it might help others. Its actually really simple with the latest version of expressjs (2.x). First create the key and cert using this code

openssl genrsa -out ssl-key.pem 1024

$ openssl req -new -key ssl-key.pem -out certrequest.csr .. bunch of prompts

$ openssl x509 -req -in certrequest.csr -signkey ssl-key.pem -out ssl-cert.pem

Store the cert and key files in the folder containing app.js. Then edit the app.js file and write the following code before express.createServer()

var https = require('https');
var fs = require('fs');

var sslkey = fs.readFileSync('ssl-key.pem');
var sslcert = fs.readFileSync('ssl-cert.pem')

var options = {
    key: sslkey,
    cert: sslcert
};

Now pass the options object in the createServer() function

express.createServer(options);

Done!

Solution 3 - node.js

First, let me see if I can clarify the problem. You are limited to one (1) node.js process, but that process can listen on two (2) network ports, both 80 and 443, right? (When you say one server it's not clear if you mean one process or only one network port).

Given that constraint, you problem seems to be, for reasons you don't provide, somehow your clients are connecting to the wrong port. This is a bizarre edge case because by default, clients will make HTTP requests to port 80 and HTTPS to port 443. And when I say "by default", I mean if no specific ports are included in the URLs. So unless you are explicitly using criss-crossed URLs like http://example.com:443 and https://example.com:80, you really shouldn't have any criss-crossed traffic hitting your site. But since you asked the question, I guess you must have it, although I bet you are using non-standard ports as opposed to the 80/443 defaults.

So, for background: YES some web servers handle this reasonably well. For example, if you do http://example.com:443 to nginx, it will respond with an HTTP 400 "Bad Request" response indicating "The plain HTTP request was sent to HTTPS port". YES, you can listen on both 80 and 443 from the same node.js process. You just need to create 2 separate instances of express.createServer(), so that's no problem. Here's a simple program to demonstrate handling both protocols.

var fs = require("fs");
var express = require("express");

var http = express.createServer();

var httpsOptions = {
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem')
};

var https = express.createServer(httpsOptions);

http.all('*', function(req, res) {
  console.log("HTTP: " + req.url);
  return res.redirect("https://" + req.headers["host"] + req.url);
});

http.error(function(error, req, res, next) {
  return console.log("HTTP error " + error + ", " + req.url);
});

https.error(function(error, req, res, next) {
  return console.log("HTTPS error " + error + ", " + req.url);
});

https.all('*', function(req, res) {
  console.log("HTTPS: " + req.url);
  return res.send("Hello, World!");
});

http.listen(80);

And I can test this via cURL like this:

$ curl --include --silent http://localhost/foo
HTTP/1.1 302 Moved Temporarily
X-Powered-By: Express
Content-Type: text/html
Location: https://localhost/foo
Connection: keep-alive
Transfer-Encoding: chunked

<p>Moved Temporarily. Redirecting to <a href="https://localhost/foo">https://localhost/foo</a></p>

$ curl --include --silent --insecure https://localhost:443/foo
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 13
Connection: keep-alive

Hello, World!%

And showing the redirect from HTTP to HTTPS...

curl --include --silent --location --insecure 'http://localhost/foo?bar=bux'
HTTP/1.1 302 Moved Temporarily
X-Powered-By: Express
Content-Type: text/html
Location: https://localhost/foo?bar=bux
Connection: keep-alive
Transfer-Encoding: chunked

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 13
Connection: keep-alive

Hello, World!%

So that will work to serve both protocols for the regular case and redirect properly. However, criss-crosses don't work at all. I believe a criss-crossed request hitting an express server isn't going to get routed through the middleware stack because it will be treated as an error from the very beginning and won't even get the request URI parsed properly, which is necessary to send it through the route middleware chain. The express stack doesn't even get them I think because they are not valid requests, so they get ignored somewhere in the node TCP stack. It's probably possible to write a server to do this, and there may already be a module out there, but you'd have to write it at the TCP layer directly. And you'd have to detect a regular HTTP request in the first chunk of client data that hits the TCP port and wire that connection to an HTTP server instead of your normal TLS handshake.

When I do either of these, my express error handlers do NOT get called.

curl  --insecure https://localhost:80/foo
curl: (35) Unknown SSL protocol error in connection to localhost:80

curl http://localhost:443/foo
curl: (52) Empty reply from server

Solution 4 - node.js

Based on Elias's answer but with inline code. This works if you have node behind nginx or a load balancer. Nginx or the load balancer will always hit node with plain old http, but it sets a header so you can distinguish.

app.use(function(req, res, next) {
  var schema = req.headers['x-forwarded-proto'];

  if (schema === 'https') {
    // Already https; don't do anything special.
    next();
  }
  else {
    // Redirect to https.
    res.redirect('https://' + req.headers.host + req.url);
  }
});

Solution 5 - node.js

http.createServer(app.handle).listen(80)

https.createServer(options, app.handle).listen(443)

for express 2x

Solution 6 - node.js

This code looks like it does what you need: https://gist.github.com/903596

Solution 7 - node.js

Try this example :

var express = require('express');
        var app = express();
        // set up a route to redirect http to https
        app.use(function (req, res, next) {
        if (!/https/.test(req.protocol)) {
            res.redirect("https://" + req.headers.host + req.url);
        } else {
            return next();
        }
    	});
        var webServer = app.listen(port, function () {
            console.log('Listening on port %d', webServer.address().port);
        });

Solution 8 - node.js

Since I was working on nginx, I had access to the header's x-forwarded-proto property so I could write a tiny middleware to redirect all traffic as described here: http://elias.kg/post/14971446990/force-ssl-with-express-js-on-heroku-nginx

Edit: Updated the url

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEliasView Question on Stackoverflow
Solution 1 - node.jsLavamantisView Answer on Stackoverflow
Solution 2 - node.jsMeetMView Answer on Stackoverflow
Solution 3 - node.jsPeter LyonsView Answer on Stackoverflow
Solution 4 - node.jsJonathan TranView Answer on Stackoverflow
Solution 5 - node.jsTJ HolowaychukView Answer on Stackoverflow
Solution 6 - node.jsrobocatView Answer on Stackoverflow
Solution 7 - node.jsAshok KumawatView Answer on Stackoverflow
Solution 8 - node.jsEliasView Answer on Stackoverflow