HTTPS in NodeJS

Why? because everything should be over HTTPS!


NB: This how-to is only meant for users on Apple Mac computers, for use in development environments, and for creating a client-server setup.

Prerequisites

  • ‘openssl’ installed, this can be done via this command
brew install openssl
  • ‘node’ installed, also done via
brew install node

Need to know

YOU’RE NOT SUPPOSED TO SERVE SELF-SIGNED CERTIFICATES.

But since this is for development purposes, and private, this doesn't matter.

To note:

  • The CN of each certificate (client and server, not the certificate authority) must be a domain, IP addresses can only be used if you have sslconfig setup to accept this.
  • You can easily add a custom domain to point to your local IP address via the hosts file for development.
  • For this tutorial l use ‘local.ldsconnect.org’ which points to ‘127.0.0.1', therefore you will need to be connected to the internet to make this example work.

1. Create a certificate authority

Generate the private key

You can just copy and paste this directly

openssl genrsa \
-out my-private-root-ca.key.pem \
2048

Since this is for private, there is no point in adding a paraphrase, but if you want to, just add the -des3 option

Self-Sign your certificate authority

Normally, one would create a certificate signing request (CSR) and then get it signed, but since this is for development purposes, we can create this CSR request and sign it ourselves in one step.

openssl req \
-x509 \
-new \
-nodes \
-key my-private-root-ca.key.pem \
-days 1024 \
-out my-private-root-ca.crt.pem \
-subj "/C=US/ST=Utah/L=Provo/O=ACME Signing Authority Inc/CN=example.co"
  • -new means your generating a new signature, this is why you don’t have to provide an in file.
  • -key is the key you’re using to sign it.
  • -nodes means “no des” or “don’t encrypt with a des cipher” or, most simply, “no password”.

2. Create and sign a server cert with your CA

Generate the server’s private key

Generate the key…looking similar?

openssl genrsa \
-out my-server.key.pem \
2048

Create a CSR

Let’s create a signing request for this key (CSR). It’s important to note that this CN MUST match YOUR domain.

openssl req -new \
-key my-server.key.pem \
-out my-server.csr.pem \
-subj "/C=US/ST=Utah/L=Provo/O=ACME Tech Inc/CN=local.ldsconnect.org"

Sign the CSR

Now we generate our server certificate, signed by our CA (ourselves).

openssl x509 \
-req -in my-server.csr.pem \
-CA my-private-root-ca.crt.pem \
-CAkey my-private-root-ca.key.pem \
-CAcreateserial \
-out my-server.crt.pem \
-days 500
  • -days should be fewer than you specified in your certificate authority

3. Create and sign a client cert with your CA

This is identical to creating and signing a server cert with your CA, therefore l will just display the code without any explanation.

openssl genrsa \
-out my-client.key.pem \
2048
# still note the CN must be your domain!
openssl req -new \
-key my-client.key.pem \
-out my-client.csr.pem \
-subj "/C=US/ST=Utah/L=Provo/O=ACME Tech Inc/CN=local.ldsconnect.org"
openssl x509 \
-req -in my-client.csr.pem \
-CA my-private-root-ca.crt.pem \
-CAkey my-private-root-ca.key.pem \
-CAcreateserial \
-out my-client.crt.pem \
-days 500

Now we have created all of our certificates required for HTTPS, the next parts are easy.


4. Creating your server and client

The Package.json file

For both the client and server, you require the exact same dependencies, which are all installed via ‘npm install —save <module>’ command:

“dependencies”: {
“body-parser”: “^1.6.5",
“ejs”: “^1.0.0",
“express”: “^4.8.5",
“morgan”: “^1.2.3" }

Therefore you should have two folders, one for client and one for server, in each of these folders should be two identical package.json files.

The servers App.js file

var https = require(‘https’);
var fs = require(‘fs’);
var express = require(‘express’);
var bodyParser = require(‘body-parser’);
var logger = require(‘morgan’);
var app = express();
var ejs = require(‘ejs’)
// configuration =================
app.set(‘view engine’, ‘ejs’);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(logger(‘dev’));

// SSL ===============
// -key: this is our private server key
// — cert: our server cert
// -ca: accept requests signed by these ca’s
var options = {
key: fs.readFileSync(‘my-server.key.pem’),
cert: fs.readFileSync(‘my-server.crt.pem’),
ca: [ fs.readFileSync(‘my-private-root-ca.crt.pem’) ] //self signed ca
};
// Routes ===============
app.get(‘/’, function(req, res) {
res.json({ msg: ‘this is the server responding’ });
});

// Server ====================
server = https.createServer(options, app).listen(3000, function() {
port = server.address().port;
console.log(‘Listening on https://127.0.0.1:' + port);
console.log(‘Listening on https://’ + server.address().address + ‘:’ + port);
console.log(‘Listening on https://local.ldsconnect.org:' + port);
});

The clients App.js file

var https = require(‘https’);
var fs = require(‘fs’);
var express = require(‘express’);
var bodyParser = require(‘body-parser’);
var logger = require(‘morgan’);
var app = express();
var ejs = require(‘ejs’)
// configuration =================
app.set(‘view engine’, ‘ejs’);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(logger(‘dev’));
// SSL =================
var options = {
key: fs.readFileSync(‘my-client.key.pem’),
cert: fs.readFileSync(‘my-client.crt.pem’)
};

// Routes ==============
var reqOptions = {
hostname: ‘local.ldsconnect.org’,
port: 3000,
path: ‘/’,
method: ‘GET’,
ca: fs.readFileSync(‘my-private-root-ca.crt.pem’)
};
options.agent = new https.Agent(reqOptions);
app.get(‘/’, function(req, res) {
var request = https.get(reqOptions, function(response) {
response.on(‘data’, function(d) {
var data = JSON.parse(d);
console.log(data);
res.render(‘view’, data);
});
});
});
// Server =============
server = https.createServer(options, app).listen(3004, function() {
port = server.address().port;
console.log(‘Listening on https://127.0.0.1:' + port);
console.log(‘Listening on https://’ + server.address().address + ‘:’ + port);
console.log(‘Listening on https://local.ldsconnect.org:' + port);
});

The clients View.ejs file

Client: <%= msg %>

Voilà! It works!

All you need to do now is run the command

node app.js

for both the client and server in two terminal windows, and then visit:

https://local.ldsconnect.org:3004

in your browser and you should see the message from the server.


All of the code for this blog post is on github here

Show your support

Clapping shows how much you appreciated Oliver Jennings’s story.