When writing code, you can call any function as long as it’s public, and similarly, you can access any object’s public properties or methods. Usually, access to code is all or none – a piece of code can be either public or private. Access to private code is typically controlled by the upper layer that encapsulates it. Although some languages support semi-private (also known as protected) object members, that’s a merely variation on the public/private theme.

Lately, I’ve been thinking about ways to implement more fine-grained access controls and have looked to the networking world for inspiration. In computer networks, firewalls are used to restrict access to network resources. For example, you can set firewall rules to allow only the application and analytics servers to access the database server – or more specifically, the port on which the database service is running. There’s no need for encapsulation, inheritance or interfaces to implement; you simply define access rules.

Unlike network firewalls, when accessing a public property or method in a program, we don’t normally check whether the caller has the right to do so. Since the callee is public, any part of the program should be able to call it without any restrictions. However, I believe there are cases when more fine-grained controls could be useful.

In the commonly used multi-tier architecture, code flows in one direction only – from the upper-most layer to the layers below. Modules can call the other modules in the same layer. They can also call modules in the layer directly below, but never above.

O I u n t p p u u t t M M M M M M M M M

How can you ensure that the modules follow these rules? Unless you’re conducting in-depth code reviews, it isn’t too difficult to end up with haphazard code paths that might have maintenance and security implications.

As a proof of concept, I’ve created a Node.js library called firewall-js using JavaScript proxies. It relies on the filesystem structure of a code base to limit access to certain parts.

Take a simple backend application with three layers: routes > controllers > services. Each layer has its own directory, and each file in a directory houses a module. The directory listing should look something like this:

> controllers
> routes
v services
   auth.js
   log.js
   user.js

Now, if you want all the controller modules (upper layer) and all other service modules (same layer) to have access to a particular service module, we can do it with a single line:

// services/user.js
// ...
const firewall = require('firewall-js');

const userService = {
    hashPassword: function (password) {
        return bcrypt.hash(password, 8);
    },

    getUserByEmail: function (email) {
        return db('user').where('email', email).then(_.head);
    },

    // ...
};

module.exports = firewall.allow(['controllers', 'services'], userService);

If you attempt to call, for example, userService.hashPassword() from a file in any other directory, an exception will be thrown:

Error: Access denied for hashPassword from /Users/me/my-app/routes/main.js:51:19

You can go one step further, and allow access not just from directories, but from files too. In the following example, only the userProfile controller can access userService, and no one else:

module.exports = firewall.allow(['controllers/userProfile.js'], userService);

I’ve deliberately made the filesystem structure the basis of the access control system. This, I believe, has two benefits:

  • A clear-cut organization of code with directories acting as layers and files as modules within those layers.
  • Permissions that are easy to understand, since most everyone is familiar with how a filesystem works.

An access control system like the one implemented here can help prevent maintenance issues, such as dependencies or coupling between unrelated parts of the application, and enhance security by limiting the potential for unauthorized actions. Additionally, it doesn’t require any drastic changes to the existing code base, as long as a sensible directory hierarchy is in place. Firewalling a module can then be as easy as adding a single line of code.

GitHub Repository: firewall-js

Discussed on:


Related: