Wednesday, June 10, 2015

Serving Your Express App With Encryption

While today's comic has nothing to do with encryption, you can actually view it (or any of my other content) using encrypted web communications. Most people refer to this as SSL, which stands for Secure Socket Layer, but SSL is an older and now non-recommended way of securing web traffic. The current method is called TLS and stands for Transport Layer Security. Doesn't make a whole lot of difference what you call it, it's the "s" in "https" when you're viewing a page with the reasonable assurance that no one is able to spy on your network traffic.

Don't mess with my network traffic!
Before last week, Amphibian.com had no way of delivering web pages in a secure manner. Why would it need to? It's just a web comic. But after my Bitcoin Paywall comic got so insanely popular I started to get people asking why I didn't have encryption enabled. Without it, there is a small chance that someone could inject their own Bitcoin address into the response from my server and take the money you are trying to send to me. So I decided to get a server certificate signed by a real authority (more on that Friday) and enable encryption for my comics.

This of course led me to create Monday's comic in which the frogs are surrounded by danger when viewed via http://amphibian.com/177 but are much safer when viewed via https://amphibian.com/177.

Now for the technical part. In order to get my Node/Express app to serve web pages over both secure and insecure web ports, 443 and 80 respectively, I had to make a few changes.

For simple apps, you are probably familiar with listening on a port this way:

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

// ... set up routes ...

var server = app.listen(3000, function() {
    console.log("listening on port %d", server.address().port);
});

But if you want the same Express instance to handle the traffic on multiple ports, you have to use the Node http module directly to create servers and pass in the Express instance as a parameter:

var express = require("express");
var http = require("http");
var app = express();

// ... set up routes ...

var server = http.createServer(app).listen(3000, function() {
    console.log("listening on port %d", server.address().port);
});

You could of course use this technique to listen on multiple insecure ports, like 3000 and 4000 as in this example:

var express = require("express");
var http = require("http");
var app = express();

// ... set up routes ...

var server1 = http.createServer(app).listen(3000, function() {
    console.log("listening on port %d", server1.address().port);
});

var server2 = http.createServer(app).listen(4000, function() {
    console.log("listening on port %d", server2.address().port);
});

However, if you want one of the listening ports to use encrypted communications you need to use the Node https module instead of http for one of them. In the simplest possible configuration it takes just one extra parameter - an options object which contains at a minimum the private key and public certificate for the server.

var express = require("express");
var http = require("http");
var https = require("https");
var fs = require("fs");
var app = express();

// ... set up routes ...

var server1 = http.createServer(app).listen(3000, function() {
    console.log("listening on port %d", server1.address().port);
});

var sslOptions = {
    key: fs.readFileSync("/path/to/private.key"),
    cert: fs.readFileSync("/path/to/server.cert")
};

var server2 = https.createServer(sslOptions, app).listen(4000, function() {
    console.log("listening securely on port %d", server2.address().port);
});

And just like that you can enable encrypted communications in your Express web app. Now read today's comic where the frogs try to avoid upsetting the apple cart...

Amphibian.com comic for 10 June 2015