Enforcing HTTPS for NodeJS apps behind an ELB

August 31, 2015    node nodejs elb https security aws

This weird problem came to be when I was trying to enforce HTTPS for a NodeJS application running behind 2 ELBs (Elastic Load Balancers) on AWS. The NodeJS application in question had sockets running for real-time communication with the client. The ELBs were configured to listen over TCP for HTTP and SSL for HTTPS and redirect to the NodeJS app listening on port 3000. Sockets need to connect over the TCP/SSL layer if you are wondering.

NodeJS app behind 2 ELBs

ELBs have support for HTTP X-Forwarded-For headers. All this does is inject 2 headers in the HTTP request so that the application running behind it knows the IP and port the client is connecting over. The problem was, since the ELBs were listening over TCP/SSL, which is the 4th layer in the OSI model and the HTTP headers are added only if the ELBs are listening over Layer 7 (the application layer). ELB has support for the Proxy Protocol Support but in order to decipher the headers, you will need to create a server using Node's net module or create any other proxy server before moving on to creating the actual http server.

Redirecting users connecting over HTTP to HTTPS requires that we check for the HTTP headers in the request which again, is available over the http-x-forwarded-for headers. The 2 images below show how the values remain undefined over the TCP/SSL layer respectively.

HTTP headers over HTTP(TCP)

HTTP headers over HTTPS(SSL)

The solution to this, after sifting through the not-so-helpful AWS documentation, was to change the ELB listening over TCP to listen over HTTP but leave the other ELB, listening over SSL, untouched. This way, the HTTP headers required to redirect users to the HTTPS version of our application would get injected while at the same time leave the socket connection intact and make sure that the ELB health checks remain working.

On the application side, we would then just need to drop in the redirections for anyone connecting over HTTP. I made a quick module - node-force-secure-redirect - which does just that for applications running on ExpressJS. The module checks for the existence of the X-Forwarded-For header and if present, check if the protocol is http. If so, then it goes ahead and redirects the user to the HTTPS version of the application.

In your Express configuration, all you would do is:

var forceHTTPS = require('node-force-secure-redirect');

var secureEnvs = ['prod', 'staging'];  

app.set('trust proxy'); // enable this to trust the proxy  
app.use(forceHTTPS(secureEnvs)); // only redirect for said envs  

Note: I or any sane engineer do not recommend using this method to host NodeJS apps behind an ELB. Please use a reverse proxy like Nginx or HAProxy. I came across this problem when I was working on an application and people were reluctant to spend 5 minutes setting up a reverse proxy.

comments powered by Disqus