Enabling HTTPS on express.js - node.js

I'm trying to get HTTPS working on express.js for node, and I can't figure it out.
This is my app.js code.
var express = require('express');
var fs = require('fs');
var privateKey = fs.readFileSync('sslcert/server.key');
var certificate = fs.readFileSync('sslcert/server.crt');
var credentials = {key: privateKey, cert: certificate};
var app = express.createServer(credentials);
app.get('/', function(req,res) {
res.send('hello');
});
app.listen(8000);
When I run it, it seems to only respond to HTTP requests.
I wrote simple vanilla node.js based HTTPS app:
var fs = require("fs"),
http = require("https");
var privateKey = fs.readFileSync('sslcert/server.key').toString();
var certificate = fs.readFileSync('sslcert/server.crt').toString();
var credentials = {key: privateKey, cert: certificate};
var server = http.createServer(credentials,function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
server.listen(8000);
And when I run this app, it does respond to HTTPS requests. Note that I don't think the toString() on the fs result matters, as I've used combinations of both and still no es bueno.
EDIT TO ADD:
For production systems, you're probably better off using Nginx or HAProxy to proxy requests to your nodejs app. You can set up nginx to handle the ssl requests and just speak http to your node app.js.
EDIT TO ADD (4/6/2015)
For systems on using AWS, you are better off using EC2 Elastic Load Balancers to handle SSL Termination, and allow regular HTTP traffic to your EC2 web servers. For further security, setup your security group such that only the ELB is allowed to send HTTP traffic to the EC2 instances, which will prevent external unencrypted HTTP traffic from hitting your machines.

In express.js (since version 3) you should use that syntax:
var fs = require('fs');
var http = require('http');
var https = require('https');
var privateKey = fs.readFileSync('sslcert/server.key', 'utf8');
var certificate = fs.readFileSync('sslcert/server.crt', 'utf8');
var credentials = {key: privateKey, cert: certificate};
var express = require('express');
var app = express();
// your express configuration here
var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);
httpServer.listen(8080);
httpsServer.listen(8443);
In that way you provide express middleware to the native http/https server
If you want your app running on ports below 1024, you will need to use sudo command (not recommended) or use a reverse proxy (e.g. nginx, haproxy).

First, you need to create selfsigned.key and selfsigned.crt files.
Go to Create a Self-Signed SSL Certificate Or do following steps.
Go to the terminal and run the following command.
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./selfsigned.key -out selfsigned.crt
After that put the following information
Country Name (2 letter code) [AU]: US
State or Province Name (full name) [Some-State]: NY
Locality Name (eg, city) []:NY
Organization Name (eg, company) [Internet Widgits Pty Ltd]: xyz (Your - Organization)
Organizational Unit Name (eg, section) []: xyz (Your Unit Name)
Common Name (e.g. server FQDN or YOUR name) []: www.xyz.com (Your URL)
Email Address []: Your email
After creation adds key & cert file in your code, and pass the options to the server.
const express = require('express');
const https = require('https');
const fs = require('fs');
const port = 3000;
var key = fs.readFileSync(__dirname + '/../certs/selfsigned.key');
var cert = fs.readFileSync(__dirname + '/../certs/selfsigned.crt');
var options = {
key: key,
cert: cert
};
app = express()
app.get('/', (req, res) => {
res.send('Now using https..');
});
var server = https.createServer(options, app);
server.listen(port, () => {
console.log("server starting on port : " + port)
});
Finally run your application using https.
More information https://github.com/sagardere/set-up-SSL-in-nodejs

I ran into a similar issue with getting SSL to work on a port other than port 443. In my case I had a bundle certificate as well as a certificate and a key. The bundle certificate is a file that holds multiple certificates, node requires that you break those certificates into separate elements of an array.
var express = require('express');
var https = require('https');
var fs = require('fs');
var options = {
ca: [fs.readFileSync(PATH_TO_BUNDLE_CERT_1), fs.readFileSync(PATH_TO_BUNDLE_CERT_2)],
cert: fs.readFileSync(PATH_TO_CERT),
key: fs.readFileSync(PATH_TO_KEY)
};
app = express()
app.get('/', function(req,res) {
res.send('hello');
});
var server = https.createServer(options, app);
server.listen(8001, function(){
console.log("server running at https://IP_ADDRESS:8001/")
});
In app.js you need to specify https and create the server accordingly. Also, make sure that the port you're trying to use is actually allowing inbound traffic.

Including Points:
SSL setup
In config/local.js
In config/env/production.js
HTTP and WS handling
The app must run on HTTP in development so we can easily debug our
app.
The app must run on HTTPS in production for security concern.
App production HTTP request should always redirect to https.
SSL configuration
In Sailsjs there are two ways to configure all the stuff, first is to configure in config folder with each one has their separate files (like database connection regarding settings lies within connections.js ). And second is configure on environment base file structure, each environment files presents in config/env folder and each file contains settings for particular env.
Sails first looks in config/env folder and then look forward to config/ *.js
Now lets setup ssl in config/local.js.
var local = {
port: process.env.PORT || 1337,
environment: process.env.NODE_ENV || 'development'
};
if (process.env.NODE_ENV == 'production') {
local.ssl = {
secureProtocol: 'SSLv23_method',
secureOptions: require('constants').SSL_OP_NO_SSLv3,
ca: require('fs').readFileSync(__dirname + '/path/to/ca.crt','ascii'),
key: require('fs').readFileSync(__dirname + '/path/to/jsbot.key','ascii'),
cert: require('fs').readFileSync(__dirname + '/path/to/jsbot.crt','ascii')
};
local.port = 443; // This port should be different than your default port
}
module.exports = local;
Alternative you can add this in config/env/production.js too. (This snippet also show how to handle multiple CARoot certi)
Or in production.js
module.exports = {
port: 443,
ssl: {
secureProtocol: 'SSLv23_method',
secureOptions: require('constants').SSL_OP_NO_SSLv3,
ca: [
require('fs').readFileSync(__dirname + '/path/to/AddTrustExternalCARoot.crt', 'ascii'),
require('fs').readFileSync(__dirname + '/path/to/COMODORSAAddTrustCA.crt', 'ascii'),
require('fs').readFileSync(__dirname + '/path/to/COMODORSADomainValidationSecureServerCA.crt', 'ascii')
],
key: require('fs').readFileSync(__dirname + '/path/to/jsbot.key', 'ascii'),
cert: require('fs').readFileSync(__dirname + '/path/to/jsbot.crt', 'ascii')
}
};
http/https & ws/wss redirection
Here ws is Web Socket and wss represent Secure Web Socket, as we set up ssl then now http and ws both requests become secure and transform to https and wss respectively.
There are many source from our app will receive request like any blog post, social media post but our server runs only on https so when any request come from http it gives “This site can’t be reached” error in client browser. And we loss our website traffic. So we must redirect http request to https, same rules allow for websocket otherwise socket will fails.
So we need to run same server on port 80 (http), and divert all request to port 443(https). Sails first compile config/bootstrap.js file before lifting server. Here we can start our express server on port 80.
In config/bootstrap.js (Create http server and redirect all request to https)
module.exports.bootstrap = function(cb) {
var express = require("express"),
app = express();
app.get('*', function(req, res) {
if (req.isSocket)
return res.redirect('wss://' + req.headers.host + req.url)
return res.redirect('https://' + req.headers.host + req.url)
}).listen(80);
cb();
};
Now you can visit http://www.yourdomain.com, it will redirect to https://www.yourdomain.com

This is how its working for me. The redirection used will redirect all the normal http as well.
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const http = require('http');
const app = express();
var request = require('request');
//For https
const https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('certificates/private.key'),
cert: fs.readFileSync('certificates/certificate.crt'),
ca: fs.readFileSync('certificates/ca_bundle.crt')
};
// API file for interacting with MongoDB
const api = require('./server/routes/api');
// Parsers
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// Angular DIST output folder
app.use(express.static(path.join(__dirname, 'dist')));
// API location
app.use('/api', api);
// Send all other requests to the Angular app
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
app.use(function(req,resp,next){
if (req.headers['x-forwarded-proto'] == 'http') {
return resp.redirect(301, 'https://' + req.headers.host + '/');
} else {
return next();
}
});
http.createServer(app).listen(80)
https.createServer(options, app).listen(443);

This answer is very similar to Setthase but its for LetsEncrypt (Ubuntu)
// Dependencies
const fs = require('fs');
const http = require('http');
const https = require('https');
const express = require('express');
const app = express();
// Certificate
const privateKey = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/privkey.pem', 'utf8');
const certificate = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/cert.pem', 'utf8');
const ca = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/chain.pem', 'utf8');
const credentials = {
key: privateKey,
cert: certificate,
ca: ca
};
app.use((req, res) => {
res.send('Hello there !');
});
// Starting both http & https servers
const httpServer = http.createServer(app);
const httpsServer = https.createServer(credentials, app);
httpServer.listen(80, () => {
console.log('HTTP Server running on port 80');
});
httpsServer.listen(443, () => {
console.log('HTTPS Server running on port 443');
});
You might encounter : EACCES: permission denied, open '/etc/letsencrypt/live/yourdeomain.com/privkey.pem'
The answer to that is here : Let's encrypt SSL couldn't start by "Error: EACCES: permission denied, open '/etc/letsencrypt/live/domain.net/privkey.pem'"
What worked for me is this in ubuntu ssh terminal
Get user whoami
// Create group with root and nodeuser as members
$ sudo addgroup nodecert
$ sudo adduser ubuntu nodecert
$ sudo adduser root nodecert
// Make the relevant letsencrypt folders owned by said group.
$ sudo chgrp -R nodecert /etc/letsencrypt/live
$ sudo chgrp -R nodecert /etc/letsencrypt/archive
// Allow group to open relevant folders
$ sudo chmod -R 750 /etc/letsencrypt/live
$ sudo chmod -R 750 /etc/letsencrypt/archive
sudo reboot

Use greenlock-express: Free SSL, Automated HTTPS
Greenlock handles certificate issuance and renewal (via Let's Encrypt) and http => https redirection, out-of-the box.
express-app.js:
var express = require('express');
var app = express();
app.use('/', function (req, res) {
res.send({ msg: "Hello, Encrypted World!" })
});
// DO NOT DO app.listen()
// Instead export your app:
module.exports = app;
server.js:
require('greenlock-express').create({
// Let's Encrypt v2 is ACME draft 11
version: 'draft-11'
, server: 'https://acme-v02.api.letsencrypt.org/directory'
// You MUST change these to valid email and domains
, email: 'john.doe#example.com'
, approveDomains: [ 'example.com', 'www.example.com' ]
, agreeTos: true
, configDir: "/path/to/project/acme/"
, app: require('./express-app.j')
, communityMember: true // Get notified of important updates
, telemetry: true // Contribute telemetry data to the project
}).listen(80, 443);
Screencast
Watch the QuickStart demonstration: https://youtu.be/e8vaR4CEZ5s
For Localhost
Just answering this ahead-of-time because it's a common follow-up question:
You can't have SSL certificates on localhost. However, you can use something like Telebit which will allow you to run local apps as real ones.
You can also use private domains with Greenlock via DNS-01 challenges, which is mentioned in the README along with various plugins which support it.
Non-standard Ports (i.e. no 80 / 443)
Read the note above about localhost - you can't use non-standard ports with Let's Encrypt either.
However, you can expose your internal non-standard ports as external standard ports via port-forward, sni-route, or use something like Telebit that does SNI-routing and port-forwarding / relaying for you.
You can also use DNS-01 challenges in which case you won't need to expose ports at all and you can also secure domains on private networks this way.

You can also generate a self-signed certificate using node-forge
In the code below, a new certificate is generated on startup, which means you will get a new certificate every time you restart the server.
const https = require('https')
const express = require('express')
const forge = require('node-forge')
;(function main() {
const server = https.createServer(
generateX509Certificate([
{ type: 6, value: 'http://localhost' },
{ type: 7, ip: '127.0.0.1' }
]),
makeExpressApp()
)
server.listen(8443, () => {
console.log('Listening on https://localhost:8443/')
})
})()
function generateX509Certificate(altNames) {
const issuer = [
{ name: 'commonName', value: 'example.com' },
{ name: 'organizationName', value: 'E Corp' },
{ name: 'organizationalUnitName', value: 'Washington Township Plant' }
]
const certificateExtensions = [
{ name: 'basicConstraints', cA: true },
{ name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true },
{ name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true },
{ name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true },
{ name: 'subjectAltName', altNames },
{ name: 'subjectKeyIdentifier' }
]
const keys = forge.pki.rsa.generateKeyPair(2048)
const cert = forge.pki.createCertificate()
cert.validity.notBefore = new Date()
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
cert.publicKey = keys.publicKey
cert.setSubject(issuer)
cert.setIssuer(issuer)
cert.setExtensions(certificateExtensions)
cert.sign(keys.privateKey)
return {
key: forge.pki.privateKeyToPem(keys.privateKey),
cert: forge.pki.certificateToPem(cert)
}
}
function makeExpressApp() {
const app = express()
app.get('/', (req, res) => {
res.json({ message: 'Hello, friend' })
})
return app
}

Don't forget your PEM pass phrase in the credentials !
When you generate your credentials with OpenSSL (don't forget the -sha256 flag) :
OpenSSL> req -x509 -sha256 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
Generating a RSA private key
writing new private key to 'key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Your JS/TS code :
const credentials = {key: privateKey, cert: certificate, passphrase: 'YOUR passphrase'};
Reference

This is my working code for express 4.0.
express 4.0 is very different from 3.0 and others.
4.0 you have /bin/www file, which you are going to add https here.
"npm start" is standard way you start express 4.0 server.
readFileSync() function should use __dirname get current directory
while require() use ./ refer to current directory.
First you put private.key and public.cert file under /bin folder,
It is same folder as WWW file.

Related

Prevent http traffic, and allow https using Automated Certificate management

I am using express nodejs module to create the app server. The app is accessibile over both https and http
I want to prevent http endpoint.
I have enabled Automated certificate management. My question is, if I want to use the cert which is generated through acm, how can I use the key and the cert in my nodejs code.
You can create an HTTPS server and pass along a private key and certificate like so:
const fs = require('fs');
const https = require('https');
const express = require('express');
const app = express();
const options = {
key: fs.readFileSync('path/to/key', 'utf8'),
cert: fs.readFileSync('path/to/cert', 'utf8'),
};
const httpsServer = https.createServer(options, app);
httpsServer.listen(8443);
If you don't want to respond to HTTP requests at all, simply don't create an HTTP server. If you want to to forward users to HTTPS instead, you could add something like this:
const http = require('http');
app.use('*', (req, res, next) => {
if (!req.secure) {
const [ host ] = req.headers.host.split(':');
return res.redirect(`https://${host}:8443${req.url}`);
}
return next();
});
const httpServer = http.createServer(app);
httpServer.listen(8000);
Or you can handle this at reverse-proxy level. E.g. for Nginx:
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
}

Disable TLS 1.0 & 1.1 OR only use TLS 1.2 and greater in Node.js Express

How can I block TLS 1.0 and TLS 1.1 on my Node.js Express server? I'm using a traditional server setup script:
const app = express();
export const server = app.listen(3000);
I'm slightly confused why I couldn't find any documentation on this.
Usually you will be running your Express server behind something else, like an Nginx proxy, an AWS Load Balancer, or some other piece of software or hardware that terminates TLS. In your example, you're not listening over HTTPS at all, you're listening for HTTP connections on port 3000. The configuration would usually be done outside of your Node app, but if you do want to do it in your Node app, it would be like this:
const express = require('express')
const https = require('https')
const fs = require('fs')
const { options } = require('crypto')
const app = express()
const opts = {
key: fs.readFileSync('/path/to/key.pem'),
cert: fs.readFileSync('/path/to/chain.pem'),
secureOptions: constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1,
}
// other app logic
// Or 443 if you run it as root, which is not recommended;
// instead you should proxy to it.
https.createServer(opts, app).listen(3443)
ex.
const https = require("https");
const https_options = {
pfx: fs.readFileSync(path.join(__dirname, "../ssl/cert.pfx")),
passphrase: "password",
minVersion: "TLSv1.2"
}
server = https.createServer(https_options, app);
minVersion is the easiest way to accomplish this as mentioned here: https://stackoverflow.com/a/62051684/4487632

Redirect non-www and HTTP to www and HTTPS

I am running an Express web server. I used Let's Encypt to create SSL certificates for both the www and non-www version of my domain. My DNS has these records:
CNAME | Host: # | Target: dynamicdns.example.com. | TTL: 60min
CNAME | Host: www | Target: dynamicdns.example.com. | TTL: 60min
I also have this node.js code to run the webserver:
// Dependencies
const express = require('express');
const fs = require('fs');
const http = require('http');
const https = require('https');
// Create express instance
const app = express();
// Get credentials from system
const privateKey = fs.readFileSync('/etc/letsencrypt/live/www.example.com/privkey.pem', 'utf8');
const certificate = fs.readFileSync('/etc/letsencrypt/live/www.example.com/cert.pem', 'utf8');
const ca = fs.readFileSync('/etc/letsencrypt/live/www.example.com/chain.pem', 'utf8');
const credentials = {
key: privateKey,
cert: certificate,
ca: ca
};
// Initalize server
app.use(express.static('public'));
const httpServer = http.createServer(app);
const httpsServer = https.createServer(credentials, app);
// Listen for HTTP
httpServer.listen(80, () => {
console.log('HTTP Server running on port 80');
});
// Listen for HTTPS
httpsServer.listen(443, () => {
console.log('HTTPS Server running on port 443');
});
This all works correctly, but it creates an issue with having multiple valid URLs for the same content. The following URLs are all valid right now:
http://example.com
http://www.example.com
https://example.com
https://www.example.com
I want to redirect all the wrong URLs to https://www.example.com, which is the correct URL. I'm not sure how I should go about doing this. Any help would be appreciated.
you could use a middleware like this one, to check all domains and redirect accordingly:
app.use((req, res, next) => {
if (!req.secure) {
return res.redirect('https://' + req.get('host') + req.url);
}
next()
})
Just make sure to use it just for production

Preparing Loopback to use SSL

I am using loopback to run the business logic of a web app, using wordpress as a front end. At the moment, when I am testing, I connect to the same server, and start loopback in a different directory, running on port 3000 at the moment. I have also opened port 3000 to remote connections
I can then connect to "example.com:3000" and "example.com:3000/explorer"
I have successfully set up the wordpress site to use the SSL certificate, and I am now looking to set loopback up the same, so that I can connect to the endpoints via https.
I have followed the directions in the following repository:
https://github.com/strongloop/loopback-example-ssl
specifically I have
Created a directory named private under the server directory.
Ran the following commands in that directory
$ openssl genrsa -out privatekey.pem 1024
$ openssl req -new -key privatekey.pem -out certrequest.csr
$ openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem
Note that I am not familiar with these commands. I did some searching, and answered the questions for the cert as best as I could
Copied the following file into the server directory as ssl-config.js
var path = require('path');
var fs = require('fs');
exports.privateKey = fs.readFileSync(path.join(__dirname, './private/privatekey.pem')).toString();
exports.certificate = fs.readFileSync(path.join(__dirname, './private/certificate.pem')).toString();
And finally I have copied over server/server.js from the example as follows
var loopback = require('loopback');
var boot = require('loopback-boot');
var http = require('http');
var https = require('https');
var sslConfig = require('./ssl-config');
var app = module.exports = loopback();
// boot scripts mount components like REST API
boot(app, __dirname);
app.start = function(httpOnly) {
if (httpOnly === undefined) {
httpOnly = process.env.HTTP;
}
var server = null;
if (!httpOnly) {
var options = {
key: sslConfig.privateKey,
cert: sslConfig.certificate,
};
server = https.createServer(options, app);
} else {
server = http.createServer(app);
}
server.listen(app.get('port'), function() {
var baseUrl = (httpOnly ? 'http://' : 'https://') + app.get('host') + ':' +
app.get('port');
app.emit('started', baseUrl);
console.log('LoopBack server listening # %s%s', baseUrl, '/');
if (app.get('loopback-component-explorer')) {
var explorerPath = app.get('loopback-component-explorer').mountPath;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
}
});
return server;
};
// start the server if `$ node server.js`
if (require.main === module) {
app.start();
}
When I start loopback, it tells me that it is running on https://0.0.0.0:3000
When I connect to https://www.example.com:3000 or https://www.example.com:3000/explorer, the endpoints are available but chrome is telling me that the connection is not secure.
I would very much like to have the wordpress site, as well as loopback using https only, but only because I understand that this is the best place to start for making the app secure.

Cannot enable my own SSL certificate on heroku

I host my website at heroku using my own domain name using node.js & express 4.x.
I have purchased SSL certificate and I want to add it to my website.
I use the following code to enable HTTPS support:
var fs = require('fs');
var http = require('http');
var https = require('https');
var privateKey = fs.readFileSync('sec/private_server.key', 'utf8');
var certificate = fs.readFileSync('sec/server.crt', 'utf8');
var credentials = { key: privateKey, cert: certificate };
// launch http server
var httpServer = http.createServer(app).listen( process.env.PORT, process.env.IP || "0.0.0.0", function() {
console.log('Listening on port %d', process.env.PORT);
});
// launch https server
var httpsServer = https.createServer(credentials, app).listen( 8443, process.env.IP || "0.0.0.0", function() {
console.log('Listening HTTPS on port 8433' );
});
Launching the server and visiting it by https link shows that I this website still uses heroku certificate.
What do I do wrong?
You need to use the Heroku SSL endpoint add-on.
https://devcenter.heroku.com/articles/ssl-endpoint

Resources