Sei sulla pagina 1di 26

9/8/2017 Implement Access Control in Node.js – Security and Node.

js

Karl Düüna Follow


Entrepreneur & Hacker by heart. CTO of http://www.nodeswat.com — researching and developing sc…
Mar 28, 2016 · 12 min read

Implement Access Control in Node.js

Most web applications rely on some sort of access control to keep


users from accessing information not meant for them. If
authentication is a lock on the main door of the hotel, then access
control is the individual access card they give to each user for
accessing their room.

We have spent quite a few blog posts on various theories about


security mechanisms for web applications (Set Up a Secure Node.js
Web Application, Unvalidated Redirects, What Do You Know About
Clickjacking? etc). In this post we will go beyond theory and take a
more hands on approach by building RBAC module from the scratch
so we can review our user’s privileges. Our aim, as usual, is to make
the web a securer place for everyone.

We will begin with a short recap on access control theory followed by


incremental how-to steps for building it.

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 1/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

So let’s get started.

. . .

Relearn Access Control
When asking developers to name different access control methods, the
usual answer seems to be ACL and RBAC. If you answered this
question the same way, then you are also among the misinformed.
Let’s look at both of these in turn and then explain why.

ACL or Access Control List is an implementation of access control,


usually represented as a table of privileges.

In this table we can see how each user is a row and has specific
privileges assigned to them. Upon access control check, the user’s row
and the column in question are cross-checked — this determines if this
user has access or not.

RBAC or Role Based Access Control is an access control method


where users are given roles and the roles determine what privileges
they have. It is usually described as a tree or diagram, as roles can
inherit accesses from their parent roles. So our previous ACL table
could look something like this:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 2/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

These are the common understandings of ACL and RBAC and they are
both incorrect. And here’s why:

First of all, ACL is not an access control model, but an implementation


type. It is often confused with IBAC (Identity Based Access Control)
where each individual has their access rights determined separately —
based on identity.

That sounds very much like the ACL we described earlier. However,
ACL variations like ACLg can also be used to implement RBAC access
model. We simply substitute the individual for a group. As a result we
end up with:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 3/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

This means ACLg (g stands for grouped) is equivalent of RBACm (m


stands for minimal). You might be wondering where is the hierarchy
in this model. Well there isn’t any. RBAC doesn’t have hierarchy
written in the basic definition — it is an added extra in a model
referred to HRBAC (Hierarchical Role Based Access Control).

So to recap: ACL is not an access control model, but an


implementation type and RBAC does not have hierarchy by the
baseline definition.

. . .

Know Your Access Control


Now that we have determined that we have some misconceptions
about the most popular access control methods, let’s take a look at
what types of access control there actually are. By the end of this
section you should have an overview of the common access control
methods and how they differ.

MAC/DAC (Mandatory/Discretionary Access Control) — although


completely separate access control methods, I grouped them together
as these two only differ in one important aspect. Both focus on the
data object as the center of access rights. Discretionary access control
method can most readily be seen in UNIX systems, where the owner
of any given file has control over whom to give access. The access
rights are in his/her discretion — hence the name. MAC also focuses
on the data object as the basis of access rights, however the rights are
not determined by the owner, but instead by the sensitivity of the data
object. This method is most often seen in governmental or military
systems due to the high costs of implementation.

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 4/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

Mandatory Access Control (MAC)

In short MAC and DAC both focus on the data object or file, whereas
DAC allows me (the owner of the file) to determine who has access.
In MAC however the access rights are determined by the
administrator or general rule.

Discretionary Access Control (DAC)

IBAC (Identity Based Access Control) — this method focuses on the


identity of the user as the basis of the privileges. Each individual is
given specific access rights for every operation. The benefits are high
granularity in assigning rights and simplicity in systems with a few

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 5/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

users. However as systems grow in user numbers, then it usually gets


difficult to manage.

Identity Based Access Control (IBAC)

RBAC (Role Based Access Control) — tries to solve the limitations of


IBAC management in large systems by mimicking the real world needs
more closely. Operational privileges are grouped into roles and each
user is assigned a role. The role, instead of the individual, is the basis
for access checks. It is often implemented in a hierarchical model,
where higher level roles inherit the privileges from lower levels. RBAC
sacrifices granularity for higher maintainability in systems with lots of
users.

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 6/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

Role Based Access Control (RBAC)

Hierarchical Role Based Access Control (HRBAC)

ABAC (Attribute Based Access Control) — is an evolution of RBAC


that tries to solve some shortcomings in specific situations. In systems
where there are many attributes that separate access to internal
resources (i.e. has the user passed some tests and been educated in
the use of this part of the system etc), using the RBAC model would
result in what is known as the role explosion — a need to define all the
roles that separate users based on their attributes. ABAC aims to solve
this problem by providing a framework for defining access rights
based on the various properties of a user.

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 7/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

Attributes Based Access Control (ABAC)

Hope you found these detailed nuances useful? In the next section we
will take a closer look at the most popular access control method of
the web — RBAC.

. . .

Details of RBAC
RBAC or Role Based Access Control is an access control method
where each identity is assigned a role and the roles determine what
access rights the identity has. This is opposed to IBAC, where each
identity has separate privilege assignment. RBAC looses some
granularity compared to IBAC, however it gains better manageability
in environments with large amounts of users.

RBAC is usually implemented as a Hierarchy of roles (HRBAC). This


allows roles to inherit privileges from other roles, which in turn makes
it easier to add new operational privileges to the whole tree.

Let’s envision an app where we have three roles: ‘Guest’, ‘Writer’,


‘Manager’. We can then illustrate the role hierarchy as follows:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 8/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

If we now want to add an edit operation, which is allowed for both


writer and manager, then all we have to do is extend the writer role:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 9/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

The definition of roles is also a welcome feature during application


development. By separating users into well defined categories
beforehand we are more easily able to model the application security.

In short RBAC is the de-facto standard access control method for most
web applications. Mainly because building a web app means that you
expect to handle a vast amount of users — thousands, millions even
billions (one can dream). Implementing IBAC in this situation would
result in enormous data duplication for access rights.

Now that we are more familiar with the logic behind RBAC, we can
proceed with our plan to build a RBAC module.

. . .

Ready, Set, Build
Having theoretical knowledge about access control is nice, but unless
put to use, we could have spent our time watching pictures of cute

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 10/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

kittens instead. So let’s not stop there and let’s start building

The logic of a basic RBAC model is simple — you define a number of


roles and each role has privileges assigned to it. When checking for
access you check if the role has access and that’s it.

So our example from before can be summed in two tables:

We can achieve this model fairly easily in JavaScript — let’s create a


model of the roles and a function to check them.

1 let roles = {
2     manager: {
3         can: ['read', 'write', 'publish']
4     },
5     writer: {
6         can: ['read', 'write']
7     },
8     guest: {
9         can: ['read']
10     }
11 }

And now we have a very simple role system. Let’s give it a


configurable and reusable form in the manner of a class.

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 11/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

1 class RBAC {
2     constructor(roles) {
3         if(typeof roles !== 'object') {
4             throw new TypeError('Expected an object as input
5         }
6         this.roles = roles;
7     }
8   
9     can(role, operation) {
10         return this.roles[role] && this.roles[role].can.indexOf

This leaves us with a very simple module for defining and checking
roles. Let’s not stop here — we will add hierarchy to the model so that
we can manage roles more easily when adding new operations to the
system.

This way there is no need to define rights to every operation for each
role separately.

It’ll allow the user to represent a list of child roles, where to inherit
permissions from.

1 let roles = {
2     manager: {
3         can: ['publish'],
4         inherits: ['writer']
5     },
6     writer: {
7         can: ['write'],
8         inherits: ['guest']
9     },
10     guest: {

And then we have to rewrite the access check functionality. In HRBAC


model, the access checking begins with the current role, checks if it
has access, if not then moves up to the parent and checks again. This
happens until a permission is found or there are no more parents to
check. So we can rewrite our checking functionality to use recursive
logic:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 12/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

1 can(role, operation) {
2     // Check if role exists
3     if(!this.roles[role]) {
4         return false;
5     }
6     let $role = this.roles[role];
7     // Check if this role has access
8     if($role.can.indexOf(operation) !== ‐1) {
9         return true;
10     }
11     // Check if there are any parents
12     if(!$role.inherits || $role.inherits.length < 1) {
13         return false;

Now we have roles, inheritance and a function to bring it together.


Almost done, but not quite there yet. There are still real use cases that
we haven’t accounted for. Let me give you an example based on a
blogging platform where a writer can create a blog post and then
open it up for editing — should the writer role also allow to rewrite
every post in the system? Probably not. We need to first check if they
are the owner of the post. But how can we write that into a reusable
definition — functions? To answer this, let’s allow operations to define
functions that need to pass.

So to extend our existing model of roles:

1 let roles = {
2     manager: {
3         can: ['publish'],
4         inherits: ['writer']
5     },
6     writer: {
7         can: ['write', {
8             name: 'edit',
9             when: function (params) {
10                 return params.user.id === params.post.owner
11             }
12         }],
13         inherits: ['guest']

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 13/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

But now our check function also needs to be rewritten — we can no


longer use indexOf either. Let’s create a function to normalise our
input for better internal use:

1 class RBAC {
2     constructor(opts) {
3         this.init(opts);
4     }
5   
6     init(roles) {
7         if(typeof roles !== 'object') {
8             throw new TypeError('Expected an object as input
9         }
10     
11         this.roles = roles;
12         let map = {};
13         Object.keys(roles).forEach(role => {
14             map[role] = {
15                 can: {}
16             };
17             if(roles[role].inherits) {
18                 map[role].inherits = roles[role].inherits;
19             }
20       
21             roles[role].can.forEach(operation => {
22                 if(typeof operation === 'string') {
23                     map[role].can[operation] = 1;
24                 } else if(typeof operation.name === 'string
25                     && typeof operation.when === 'function'

And now we can use the map we created in our check function:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 14/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

1 can(role, operation, params) {
2     // Check if role exists
3     if(!this.roles[role]) {
4         return false;
5     }
6     let $role = this.roles[role];
7     // Check if this role has this operation
8     if($role.can[operation]) {
9         // Not a function so we are good
10         if(typeof $role.can[operation] !== 'function') {
11             return true;
12         }
13         // If the function check passes return true
14         if($role.can[operation](params)) {
15             return true;
16         }
17     }
18   

Awesome! We now have RBAC class that we can use to check our
defined hierarchy model. Additionally, we can also define functions to
do dynamic checks for specific access:

RBAC.can('writer', 'edit', {user: user, post: post});

We are still not done. Let’s not forget that we are dealing with Node.js
so synchronous solutions are not the best way to go — we need async
so that we can instantiate the class with information found in the
database. Or we might want our access check to look something up
from the file system, other API or somewhere else. Point is — we need
it.

We can provide this in two ways — with promises or callbacks.


Because we want to be supportive of both styles let’s implement both.
However transformation from Promise to callback is much easier than
vice versa so we’ll use Promises internally.

We’ll start our update with the check function. Let’s use the Q module
to provide backwards compatibility. We can just wrap the contents of

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 15/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

our function in a promise constructor:

1 let Q = require('q');
2
3 // ... //
4
5 can(role, operation, params) {
6     return Q.Promise((resolve, reject) => {
7         // our function
8         // ... //
9     });

We can then handle callbacks by optionally binding the handlers for


our promise.

1 let Q = require('q');
2 can(role, operation, params, cb) {
3     let callback = cb || () => {};
4     return Q.Promise((resolvePromise, rejectPromise) => {
5
6         // Collect resolve handling
7         function resolve(value) {
8             resolvePromise(result);
9             callback(undefined, result);
10         }
11
12         // Collect error handling
13         function reject(err) {
14             rejectPromise(err);
15             callback(err);

We can internally handle the resolve/reject events, by specifying a


callback ourselves

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 16/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

1 $role.can[operation](params, function (err, result) { 
2     if(err) { 
3         return reject(err); 
4     } 
5     if(!result) { 
6         return reject(false); 
7     } 

And we can handle the inheritance by creating a new promise. One


that resolves when any one of the child promises resolves — aka use
Q.any.

1 return Q.any($role.inherits.map(child => this.can(child, operation, params)))
2     .then(resolve, reject);

After adding some type checks, our can function could look
something like this:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 17/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

1 can(role, operation, params, cb) {
2
3     if(typeof params === 'function') {
4         cb = params;
5         params = undefined;
6     }
7
8     let callback = cb || () => {};
9
10     return Q.Promise((resolve, reject) => {
11
12         // Collect resolve handling
13         function resolve(value) {
14             resolvePromise(result);
15             callback(undefined, result);
16         }
17
18         // Collect error handling
19         function reject(err) {
20             rejectPromise(err);
21             callback(err);
22         }
23
24         if (typeof role !== 'string') {
25             throw new TypeError('Expected first parameter to be string : role
26         }
27
28         if (typeof operation !== 'string') {
29             throw new TypeError('Expected second parameter to be string : operation
30         }
31
32         let $role = $this.roles[role];
33
34         if (!$role) {
35             throw new Error('Undefined role');
36         }
37
38         // IF this operation is not defined at current level try higher
39         if (!$role.can[operation]) {
40             // If no parents reject
41             if (!$role.inherits) {
42                 return reject(false);

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 18/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

42                 return reject(false);
43             }
44             // Return if any parent resolves true or all reject
45             return Q.any($role.inherits.map(parent => this.

Now we are almost done. The last thing we want to support is


asynchronous loading of the definitions of roles. This means we have
to handle the initialisation. The easiest way to accept a function as an
input that can return the configuration object after obtaining it
somewhere. To do this, let’s add a check in the beginning of init
function and store the resolve state in a variable:

// If opts is a function execute for async loading

if(typeof roles === 'function') {


this._init = Q.nfcall(roles)
.then(data => this.init(data));
return;
}

And add $this._inited = true before the return statement.

Now we can check at the beginning of the can function if we have


managed to set up our roles and act accordingly:

// If not inited then wait until init finishes

if(!this._inited) {
return this._init
.then(() => this.can(role, operation, params,
cb));
}

And now we are done on the functionality part.

. . .

Start Controlling Access

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 19/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

In the previous section we built a nice and simple access control


module that I have published under the name easy-rbac. In this
section we will look at how to add proper role based access control to
an express application using a combination of easy-session (a session
handling module I have written some time ago) and easy-rbac (the
module we built, which is now integrated to easy-session).

First we will set up an express application with easy-session and a few


routes for us to test our sessions:

1 'use strict';
2
3 let express = require('express');
4 let session = require('express‐session');
5 let eSession = require('easy‐session');
6 let cookieParser = require('cookie‐parser');
7
8 let app = express();
9
10 app.use(cookieParser());
11 app.use(session({
12     secret: 'keyboard cat',
13     resave: false,
14     saveUninitialized: true
15 }));
16 app.use(eSession.main(session));
17
18 // Add a path to allow easy login to any role
19 app.get('/login/:role', function (req, res, next) {
20     req.session.login(req.params.role, function () {
21         res.redirect('/');
22     });
23 });
24

Now we have our base setup, but what if our application is expanding
— we are adding functionality to read and write blog posts; everyone
can read and writers can write? We could do it by checking the role
like this:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 20/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

1 // We need no authentication here
2 app.get('/blog', function (req, res, next) {
3     res.send('Cool blog post');
4 });
5
6 app.get('/blog/create', function (req, res, next) {
7     // Check if user is writer
8     if(!req.session.hasRole('writer')) {
9         res.sendStatus(403);
10         return;

This is, however, one of the most common mistakes made in


implementing RBAC — looking for specific roles instead of
validating operations. It is not scalable. What happens when we
create new roles that are also supposed to be able to create blog
posts? We would have to come back and rewrite this logic all the
time. Not good.

Instead, we should be focusing on operations — roles can be added,


hierarchies change, but if we always check for ‘post:create’ (the
notation ‘blog:create’ has no technical implementation value, the
semantics just help organise and keep a consistent naming), then we
won’t have to change our code. But in order to do that, we will need
to configure our integrated easy-rbac.

1 app.use(eSession.main(session, {
2     rbac: {
3         guest: {
4             can: ['blog:read']
5         },
6         writer: {
7             can: ['blog:create'],
8             inherits: ['guest']

Or if we want to store our role logic in the database layer, so that it is


centralised across application instances, we can set up an async
function to retrieve it:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 21/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

1 app.use(eSession.main(session, {
2     rbac: function (cb) {
3         // Our async logic
4         setImmediate(cb, null, {
5             guest: {
6                 can: ['blog:read']
7             },
8             writer: {
9                 can: ['blog:create'],
10                 inherits: ['guest']

And now we can check for the right to create blog posts.

1 app.get('/blog/create', function (req, res, next) {
2     // Check if user has access
3     req.session.can('blog:create')
4         .then(function () {
5             res.send('Blog edit');
6         })
7         .catch(function () {
8             res.sendStatus(403);

Even better, let’s move the validation into a middleware to keep our
logic clean.

1 app.get('/blog/create', eSession.can('blog:create'), function
2     res.send('Blog edit');
3 });

Awesome — heading in the right direction. Let’s now set up an edit


path as well. However, here we can’t just check if the user is a writer
any more. I wouldn’t want some other writer to change my posts. So
we are going to have to set up conditions by changing the writer’s role
definition:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 22/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

1 app.use(eSession.main(session, {
2     rbac: {
3         guest: {
4             can: ['blog:read']
5         },
6         writer: {
7             can: ['blog:create', {
8                 name: 'blog:edit',
9                 when: function (params, cb) {
10                     //check if user is the owner
11                     setImmediate(cb, null, params.user.id ===
12                 }

We’ll also need the user on the session object:

1 app.get('/login/:role', function (req, res, next) {
2     // Going to hardcode the user object
3     let extend = {
4         user: {
5             id: 2
6         }
7     };
8     req.session.login(req.params.role, extend, function () {

And finally we need a way to look up a blog object and test if we can
actually edit:

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 23/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

1 let $q = require('q');
2 function findBlog(id) {
3     return $q({
4         ownerId: parseInt(id)
5     });
6 }
7
8 app.get('/blog/edit/:id', function (req, res, next) {
9     // look for blog
10     findBlog(req.params.id)
11         .then(function (blog) {
12             //check for access
13             return req.session.can('blog:edit', {user: req.
14         }, function (err) {
15             // Handling db errors
16             res.sendStatus(500);
17         })

Again we can do this with middleware

1 function getParams(req, res, cb) {
2     findBlog(req.params.id)
3         .then(function (blog) {
4             cb(null, {
5                 user: req.session.user,
6                 blog: blog
7             });
8         }, cb);
9 }
10

And there we have it. A nice access control setup that we can easily
reuse throughout our application.

. . .

Wrapping Up
In this post we looked at various access control methods and
debunked some common misconceptions along the way. You should

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 24/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

now know the key methodologies and how they differ.

In the second half we got our hands dirty. We started by coding an


access control module and then implemented access control for a
simple test application using easy-session. All in a days work.

In the future, you should know to avoid checking roles directly and
focus on operations instead. Also, I sincerely hope that you won’t
forget to add access checks where needed.

. . .

If you found this post useful and want a more thorough overview of
authentication, access control methods and other Node.js security
topics, I recommend you read my book Secure Your Node.js Web
Application: Keep Attackers Out and Users Happy

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 25/26
9/8/2017 Implement Access Control in Node.js – Security and Node.js

https://blog.nodeswat.com/implement-access-control-in-node-js-8567e7b484d1 26/26

Potrebbero piacerti anche