At Movable Ink, we have a number of different backend services,  many of which have administrative web interfaces. We want to give  employees access to these services without opening the services up to  the world, so we need to authenticate user access. Some of the services have built-in authentication, but few support delegated authentication and we didn’t want to have to manage users in many different places.

All of our services live firewalled off inside VPCs in Amazon’s EC2.  One method for allowing access might be to let our employees set up VPN  connections into our datacenters. With the VPN connected, employees  could make internal requests via direct access to our internal network.

The problem with that approach is that we have different levels of  authorization within our organization. Someone on our devops team may need direct access into our internal network, but many of our employees may  just need access to a handful of web services. It’s possible to  implement fine-grained access control using networking, but it is  complicated and error-prone. Google recently blogged about the same drawbacks.

We developed Doorman to act as an authenticating proxy between the world and our internal services. It’s written in node.js, and leans heavily on node-http-proxy for proxying and everyauth for service authentication. We use express’s middleware pattern to chain everything together. An annotated excerpt from Doorman:

var app = express();

// Log the request to stdout
app.use(logMiddleware);

// Redirect http requests to https
app.use(tls);

// If the user is already authenticated, read their signed cookie
app.use(cookieParser(conf.sessionSecret));

// Create the user's session from the cookie
app.use(doormanSession);

// Display flash messages when appropriate
app.use(flash());

// Ensure the user is valid and allowed to access the service, this
// also halts the chain and proxies to the internal service if successful
app.use(checkUser);

// Parse POST bodies
app.use(bodyParser.urlencoded({extended: false}));

// Register paths for oAuth redirects and callbacks
app.use(everyauth.middleware());

// `/_doorman` serves assets for doorman login form
app.use(express.static(__dirname + "/public", {maxAge: 0 }));

// display the login page if the user isn't authenticated
app.use(loginPage);

Each middleware runs successively and has the opportunity to modify  the request/response, halt it and return, or continue down the chain.  For example, the tls middleware tells the response to send a 301 Moved and halts the chain, while cookieParser tacks some information onto the request object and continues down to the next middleware. In the event none of  the middlewares halt the chain and finish the response, we serve a 404 Not Found.

One particular challenge we ran into was how to use Doorman for  multiple internal services at once. A simple way would be to run a  separate Doorman app for each internal service, but it’s often overkill  to have a dedicated Doorman for each service. We’re planning on  introducing multiple domain support, which can route to different  internal services based on the hostname, and have different access  control for each service. Due to SSL limitations, it will likely require  that all of the internal services be on different subdomains and have  Doorman configured to use a wildcard certificate. Another possibility  would be to use LetsEncrypt with SNI support.

If you’re interested in using or contributing to Doorman, you can clone it on Github.