Blog of Raivo Laanemets

Stories about web development, consulting and personal computers.

Client IP in Express behind Nginx

On 2016-07-23

Last week I worked on a web application that needed to restrict some pages and routes to specific IP addresses. This is considered security through obscurity but still works for simpler non-critical cases.

The application is reverse proxied through Nginx and does not see client's real IP addresses directly (it sees the proxy's). The actual client IP address is passed through the header X-Real-IP by using the proxy_set_header directive in the reverse proxy configuration:

location / {
    proxy_pass http://127.0.0.1:9091;
    proxy_set_header X-Real-IP $remote_addr;
    # ... rest of configuration
}

The app runs without a proxy in some configurations (development, test) and that requires IP check inside the app itself. Otherwise the check could have been implemented on Nginx as well.

At the beginning I started to look at the existing Node.js packages to obtain the client's IP. One of such packages was ipware. It seems to be a port from some other platform's similar library. It did not work for me. There seems to be some confusion over HTTP header normalization in Node.

Other such libraries seem to be quite complex as well and contain too much magic. I ended up using my own middleware that sets req.clientIP from the header when it's given. The IP address is taken from the client socket when the header does not exist. This is done with a middleware:

module.exports = function () {
  return function (req, res, next) {
    var realIP = req.get("x-real-ip");
    if (realIP) {
      req.clientIP = realIP;
    } else {
      req.clientIP = req.socket.remoteAddress;
    }
    next();
  };
};

The HTTP header normalization in Node is documented in the API reference:

Header names are lower-cased.

There is no transform that replaces dashes with underscores or does some other fancy stuff.

Security warning

In the current application I also added proper password-based authentication and authorization. The approach with IP aadress inside an header has a potential security hole: when the app is not behind Nginx then anyone can set their "IP address" by setting the X-Real-IP request header!