Category: node.js
Use SSL Client Authentication with node.js and Express
For a recent engagement, I was asked to set up client authentication for a node.js/express app. With this type of authentication, an SSL certificate is required to access the site.
To get the most out this article, download and run the accompanying this demonstration app. It contains scripts to create the necessary certificates, similar to what you see here. These scripts and this article use openssl, which can be found in Linux or the Git for Windows bash shell.
For these purposes you don’t need to understand everything about certs, but here are some basics:
Create a signing key (CA)
Everything starts with a signing key, which can be created with a command similar to this:
openssl req -new -x509 -days 365 -keyout ca-key.pem -out ca-crt.pem
The resulting cert and key can then be used to sign other certificates. Creating a self signed cert is similar.
Create a signed certificate
Creating a signed certificate is a multi-step process. First you’ll need a signing key, as generated above.
export CERT=server export CA_NAME=ca #generating private key for server openssl genrsa -out $CERT-key.pem 4096 #generate a signing request openssl req -new -sha256 -config $CERT.cnf -key $CERT-key.pem -out $CERT-csr.pem #perform the signing openssl x509 -req -days 365 -in $CERT-csr.pem -CA $CA_NAME-crt.pem -CAkey $CA_NAME-key.pem -CAcreateserial -out $CERT-crt.pem
The initial command (genrsa) generates public and private keys. The third command’s final product, server-crt.pem, is used along with the private key to run ssh in express.
Set up https
To start, your express app must be running https, so you’ll need to have (or generate) a public and private key for that. Here is an example of how to set that up:
var express = require('express'); var fs = require('fs'); var https = require('https'); var app = express(); var options = { key: fs.readFileSync('certs/server-key.pem'), cert: fs.readFileSync('certs/server-crt.pem'), }; app.use(function (req, res, next) { res.writeHead(200); res.end("hello world\n"); next(); }); var listener = https.createServer(options, app).listen(4433, function () { console.log('Express HTTPS server listening on port ' + listener.address().port); });
Test https
$ node app Express HTTPS server listening on port 4433
Test using curl in another window. Note that we use -k to bypass certificate validation.
$ curl https://127.0.0.1:4433 -k hello world
Set up client authentication
Once you have ssh cooperating, you can add client auth. requestCert will require the client (e.g. curl, browser) to send a cert. rejectUnauthorized=false allows us to handle validation ourselves.
If you’re using a browser,
You’ll notice that now we have the client auth certificate. If this was created as a CA, then ANY certificate which is signed by this one will validate.
var options = { key: fs.readFileSync('certs/server-key.pem'), cert: fs.readFileSync('certs/server-crt.pem'), ca: fs.readFileSync('certs/ca.pem'), #client auth ca OR cert requestCert: true, #new rejectUnauthorized: false #new };
Here’s how we handle the validation:
app.use(function (req, res, next) { if (!req.client.authorized) { return res.status(401).send('User is not authorized'); } #examine the cert itself, and even validate based on that! var cert = req.socket.getPeerCertificate(); if (cert.subject) { console.log(cert.subject.CN); } next(); });
Test client authentication
# Access the site using the client certificate created above. curl -v -s -k --key certs/client1-key.pem --cert certs/client1-crt.pem https://localhost:4433
Test client authentication with a browser
- Add the client pfx file to your certificate store. If you’re using a newly created CA, you might need to add its pfx as well. If you’re not presented with a dialog box in step 3, this is likely the problem.
- Browse to https://localhost:4433.
- You should be presented with a dialog box. Choose the client certificate.
- Note that if you change the certificate, you will need to completely close Chrome (and possibly need to kill the task) in order to see the dialog again.
Certificates CAN be revoked. This is not covered in this article, but see the appropriate section here.
Other articles you might like:
HTTPS Authorized Certs with Node.js
How To Create an SSH CA to Validate Hosts and Clients with Ubuntu
node.js deep dive: Passing config variables through jade
Using a MEAN stack knockoff, I needed to pass variables configured in the server environment, to make them accessible to client side javascript. In doing so I had to tangle with nconf and jade.
Here was my solution.
My config looks like this:
{ NODE_ENV : development, ... ui_varables { var1: one, var2: two } }
First I had to make sure that the necessary config variables were being passed. MEAN uses the node nconf package, and by default is set up to limit which variables get passed from the environment. I had to remedy that:
config/config.js:
original:
nconf.argv() .env(['PORT', 'NODE_ENV', 'FORCE_DB_SYNC'] ) // Load only these environment variables .defaults({ store: { NODE_ENV: 'development' } });
after modifications:
nconf.argv() .env('__') // Load ALL environment variables // double-underscore replaces : as a way to denote hierarchy .defaults({ store: { NODE_ENV: 'development' } });
Now I can set my variables like this:
export ui_varables__var1=first-value export ui_varables__var2=second-value
Note: I reset the “heirarchy indicator” to “__” (double underscore) because its default was “:”, which makes variables more difficult to set from bash.
Now the jade part:
Next the values need to be rendered, so that javascript can pick them up on the client side. A straightforward way to write these values to the index file. Because this is a one-page app (angular), this page is always loaded first. I think ideally this should be a javascript include file (just to keep things clean), but this is good for a demo.
app/controllers/index.js:
'use strict'; /** * Module dependencies. */ var _ = require('lodash'); var config = require('../../config/config'); exports.render = function(req, res) { res.render('index', { user: req.user ? JSON.stringify(req.user) : "null", //new lines follow: config_defaults : { ui_defaults: JSON.stringify(config.configwriter_ui).replace(/<\//g, '<\\/') //NOTE: the replace is xss prevention } }); };
app/views/index.jade:
extends layouts/default block content section(ui-view) script(type="text/javascript"). window.user = !{user}; //new line here defaults = !{config_defaults.ui_defaults};
In my rendered html, this gives me a nice little script:
<script type="text/javascript"> window.user = null; defaults = {"var1":"first-value","var2:"second-value"}; </script>
From this point it’s easy for angular to utilize the code.