Building RBAC in Node
Tags: authorization, node, rbac
Published: 2022-02-03 19:28:49

## Introduction

Role Based Access Control (RBAC) is an access control pattern that governs the way users access applications based on the roles they are assigned. Roles are essentially groupings of permissions to perform operations on particular resources. Instead of assigning numerous permissions to each user, RBAC allows users to be assigned a role that grants them access to a set of resources. For example, a role could be something like `evilGenius`, or a `sidekick`. A `sidekick` like Morty Smith for example could have the permission to `gather mega seeds`, and an `evilGenius` like Rick would be able to `create` a `microverse`.

![creating-a-microverse](https://qph.cf2.quoracdn.net/main-qimg-5089011d6abd9877b22383c1e54d66d7)

In this post, we'll review some of the ways to implement an RBAC pattern in a Node.js application using several open source libraries as well as the Aserto Express.js SDK. This is by no means an exhaustive guide for all the features the libraries provide, but it should give you a good idea of how to use them.

![too-many-rick](https://y.yarn.co/3a8599a9-30a5-49d7-8dbe-208d49aad7e6_text.gif)

## Prerequisites

- You'll need a basic understanding of Javascript and Node.js to follow this post.
- You'll need [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/) installed on your machine.
- You should be familiar with Rick and Morty - otherwise these users are going to make no sense ;-)

## Setup

The code examples shown below can be found in [this repository](https://github.com/aserto-demo/node-rbac). To run each of them, navigate to the corresponding directory and run `yarn install` followed by `yarn start`.

All of the examples we'll demonstrate in this post have a similar structure:

- They use Express.js as the web server, and they use a middleware called `hasPermission` to check if the user has the correct permissions to access the route.
- They share a `users.json` file that contains the users and their assigned roles. This file will simulate a database that would be used in a real application to store and retrieve user information.

```json
[
  {
    "id": "beth@the-smiths.com",
    "roles": ["clone"]
  },
  {
    "id": "morty@the-citadel.com",
    "roles": ["sidekick"]
  },
  {
    "id": "rick@the-citadel.com",
    "roles": ["evilGenius", "squanch"]
  }
]
```

- The `users.json` file is going to be accessed by a function called `resolveUserRole` which, given a user will resolve their role. This function is shared by all of the examples and is found in `utils.js`.

```js
const users = require("./users");
const resolveUserRole = (user) => {
  //Would query DB
  const userWithRole = users.find((u) => u.id === user.id);
  return userWithRole.role;
};
```

- The initial setup for the Express.js app is straightforward:

```js
const express = require("express");
const { resolveUserRoles } = require("../utils");
const app = express();
app.use(express.json());
```

- The application will have three routes that will be protected by the `hasPermission` middleware, which will determine whether the user has the correct permissions to access the route, based on the action associated with that route.

```js
app.get("/api/:asset", hasPermission("gather"), (req, res) => {
  res.send("Got Permission");
});

app.put("/api/:asset", hasPermission("consume"), (req, res) => {
  res.send("Got Permission");
});

app.delete("/api/:asset", hasPermission("destroy"), (req, res) => {
  res.send("Got Permission");
});
```

- And finally, the application will listen on port 8080:

```js
app.listen(8080, () => {
  console.log("listening on port 8080");
});
```

### Testing

To test the application, we'll make a set of requests to the routes and check the responses:

```
curl -X <HTTP Verb> --location 'http://localhost:8080/api/<asset>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "user": {
        "id": "krisj@acmecorp.com"
    }

}'
```

Where `<HTTP Verb>` is either `GET`, `PUT`, or `DELETE` and `<asset>` is either `megaSeeds` or `timeCrystals`.

For each user, we'll expect the following:

- Beth (AKA the `clone`): Should be only able to `gather` `megaSeeds` and `timeCrystals`
- Morty (AKA the `sidekick`): Should be only able to `gather` and `consume` `megaSeeds` and `timeCrystals`
- Rick (AKA the `evilGenius`): Should be able to `gather`, `consume` and `destroy` only `megaSeeds` and `timeCrystals`.

Let's go get those mega seeds!

![mega-seeds](https://pa1.narvii.com/6302/3cdf3ccd32e76952e68ecbc6bcb5d3738a20152a_hq.gif)

## Vanilla Node.js

To set the scene, we start with the most simplistic way of enforcing roles in a Node.js application. In this example, we're going to use a JSON file (`roles.json`) that will map specific roles to actions they may perform, and assets they may perform those actions on:

```json
{
  "clone": {
    "gather": ["megaSeeds", "timeCrystals"]
  },
  "sidekick": {
    "gather": ["megaSeeds", "timeCrystals"],
    "consume": ["megaSeeds", "timeCrystals"]
  },
  "evilGenius": {
    "gather": ["megaSeeds", "timeCrystals"],
    "consume": ["megaSeeds", "timeCrystals"],
    "destroy": ["megaSeeds", "timeCrystals"]
  }
}
```

In this JSON snippet, the `clone` role will only be able to `gather` the `megaSeeds` and `timeCrystals` assets. The `sidekick` role will be able to `gather` and `consume` the `megaSeeds` and `timeCrystals` assets. The `evilGenius` role will be able to `gather`, `consume`, and `destroy` `megaSeeds` and `timeCrystals`.

The implementation of the `hasPermission` middleware function is going to be very simple:

```js
const hasPermission = (action) => {
  return (req, res, next) => {
    const { user } = req.body;
    const { asset } = req.params;
    const userRoles = resolveUserRoles(user);

    const permissions = userRoles.reduce((perms, role) => {
      perms =
        roles[role] && roles[role][action]
          ? perms.concat(roles[role][action])
          : perms.concat([]);
      return perms;
    }, []);

    const allowed = permissions.includes(asset);

    allowed ? next() : res.status(403).send("Forbidden").end();
  };
};
```

In this example we:

1. Iterate over each user role
2. Check the existence of the user's given `role` in the `roles` object
3. Check the existence of `actions` within that given role, and finally check if the assets array associated with that role and action contains the asset the user is trying to access.
4. Determine whether the permissions the user has included the asset they are trying to access.

Other than being pretty simplistic, this approach is not going to be very scalable - the "policy" definition is going to become complex, highly repetitive, and thus hard to maintain.

Click [here](https://github.com/aserto-demo/node-rbac/tree/main/vanilla) to view the full vanilla Node.js implementation.

## [Node-Casbin](https://github.com/casbin/node-casbin)

Casbin is a powerful and efficient open-source access control library. It has SDKs in many languages, including Javascript, Go, Rust, Python, and more. It provides support for enforcing authorization based on various access control models: from a classic "subject-object-action" model, through RBAC and ABAC models to fully customizable models. It has support for many adapters for policy storage.

In Casbin, the access control model is encapsulated in a configuration file (`src/rbac_model.conf`):

```
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[matchers]
m = g(r.sub , p.sub) && r.obj == p.obj && r.act == p.act

[policy_effect]
e = some(where (p.eft == allow))
```

Along with a policy/roles definition file (`src/rbac_policy.conf`)

```csv
p, clone, megaSeeds, gather
p, clone, timeCrystals, gather
p, sidekick, megaSeeds, consume
p, sidekick, timeCrystals, consume
p, evilGenius, megaSeeds, destroy
p, evilGenius, timeCrystals, destroy
g, sidekick, clone
g, evilGenius, sidekick
```

- The `request_definition` section defines the request parameters. In this case, the request parameters are the minimally required parameters: subject (`sub`), object (`obj`) and action (`act`). It defines the parameters' names and order that the policy matcher will use to match the request.

- The `policy_definitions` section dictates the structure of the policy. In our example, the structure matches that of the request, containing the subject, object, and action parameters. In the policy/roles definition file, we can see that there are policies (on lines beginning with `p`) for each role (`clone`, `sidekick`, and `evilGenius`)

- The `role_definition` section is specific to the RBAC model. In our example, the model indicates that an inheritance group (g) is comprised of two members. In the policy/roles definition file, we can see two role inheritance rules for `sidekick` and `evilGenius`, where `sidekick` inherits from `clone` and `evilGenius` inherits from `sidekick` (which means the `evilGenius` will also have the `clone` permissions).

- The `matchers` sections defines the matching rules for policy and the request. In our example, the matcher is going to check whether each of the request parameters matches the policy parameters and that the role `r.sub` is in the policy.

The implementation of the `hasPermission` middleware function for Node-Casbin is as follows:

```js
const hasPermission = (action) => {
  return async (req, res, next) => {
    const { user } = req.body;
    const { asset } = req.params;
    const userRoles = resolveUserRoles(user);

    const e = await newEnforcer("./rbac_model.conf", "./rbac_policy.csv");

    const allowed = await userRoles.reduce(async (perms, role) => {
      const acc = await perms;
      if (acc) return true;
      const can = await e.enforce(role, asset, action);
      if (can) return true;
    }, false);

    allowed ? next() : res.status(403).send("Forbidden").end();
  };
};
```

In this code snippet, we create a new Casbin enforcer using the `newEnforcer` function. Then, we call `e.enforce(role, asset, action)` on each user role, and return `true` as soon as the result of the `e.enforce` function is `true`. We return a `403 Forbidden` response if the user is not allowed to perform the action on the asset, otherwise, we call the `next` function to continue the middleware chain.

Click [here](https://github.com/aserto-demo/node-rbac/tree/main/casbin) to view the full Node-Casbin implementation.

## [CASL](https://github.com/stalniy/casl)

The CASL library is an isomorphic authorization that's designed to be incrementally adoptable. Its aim is to make it easy to share permissions across UI components, API services, and database queries. CASL doesn't have the concept of a role - it can only assign a set of permission to a user. It is the responsibility of the developer to handle to the assignment of the proper permissions to a user based on their assigned roles. Instead, CASL permissions are defined as tuples of "action", "subject", "conditions" and optionally "fields".

The main concept in CASL is the "Ability", which determines what a user is able to do in the applications.

![show-me-what-you-got](https://c.tenor.com/r6zafkpW15oAAAAC/show-me-what-you-got-rick-and-morty.gif)

It uses a declarative syntax to define abilities, as seen below:

```js
import { AbilityBuilder, Ability } from "@casl/ability";
import { resolveUserRoles } from "../utils.js";

export function defineRulesFor(user) {
  const { can, rules } = new AbilityBuilder(Ability);

  // If no user, no rules
  if (!user) return new Ability(rules);
  const roles = resolveUserRoles(user);

  roles.forEach((role) => {
    switch (role) {
      case "clone":
        can("gather", "Asset", { id: "megaSeeds" });
        can("gather", "Asset", { id: "timeCrystals" });
        break;
      case "sidekick":
        can("gather", "Asset", { id: "megaSeeds" });
        can("gather", "Asset", { id: "timeCrystals" });
        can("consume", "Asset", { id: "timeCrystals" });
        can("consume", "Asset", { id: "megaSeeds" });
        break;
      case "evilGenius":
        can("manage", "all");
        break;
      default:
        // anonymous users can't do anything
        can();
        break;
    }
  });

  return new Ability(rules);
}
```

In this code snippet, we resolve the user's role using the same `resolveUserRoles` utility function. Since CASL doesn't have the notion of a role, we create a `switch` statement that handles the assignment of permission for the various roles. For each role we call the `can` function which assigns a particular action (`gather`, `consume`, or `destroy`) to a particular resource model (`Asset`) with specific conditions (`id` has to equal the asset specified). In the case of the `evilGenius` role, we use the reserved `manage` keyword - which means the user can perform all actions, and the reserved `all` keyword that indicates that this role can do execute actions on all assets.

The `hasPermission` middleware function for CASL is very similar to the one we used in the previous example:

```js
const hasPermission = (action) => {
  return (req, res, next) => {
    const { user } = req.body;
    const { asset: assetId } = req.params;
    const ability = defineRulesFor(user);
    const asset = new Resource(assetId);
    try {
      ForbiddenError.from(ability).throwUnlessCan(action, asset);
      next();
    } catch (error) {
      res.status(403).send("Forbidden").end();
    }
  };
};
```

The `ability` is defined by the rules set by the `defineRulesFor` function. Then, we wrap the error handler `ForbiddenError.from(ability)...` that will throw unless that ability allows the user to perform the `action` on the `asset` we pass to it. If no error is thrown, we call the `next` function to continue the middleware chain, otherwise, we return a `403 Forbidden` response.

Click [here](https://github.com/aserto-demo/node-rbac/tree/main/casl) to view the full CASL implementation.

## [RBAC](https://github.com/seeden/rbac)

The `rbac` library provides a simple interface for RBAC authorization. It provides an asynchronous interface for the storage of the policy and supports hierarchical roles.

The policy definition is a JSON object passed to the `RBAC` constructor:

```js
const { RBAC } = require("rbac");
const policy = new RBAC({
  roles: ["clone", "sidekick", "evilGenius"],
  permissions: {
    megaSeeds: ["gather", "consume", "destroy"],
    timeCrystals: ["gather", "consume", "destroy"],
  },
  grants: {
    clone: ["gather_megaSeeds", "gather_timeCrystals"],
    sidekick: ["clone", "consume_megaSeeds", "consume_timeCrystals"],
    evilGenius: ["sidekick", "destroy_megaSeeds", "destroy_timeCrystals"],
  },
});
```

This code snippet defines the possible roles used in the policy, the possible actions for each asset and eventually defines the mapping between the possible roles and the combination of actions and assets. The combination of actions and assets is simply the concatenation of the action string, an underscore, and the asset. We can see that `sidekick` also inherits the `clone` role, and `evilGenius` also inherits the `sidekick` role.

The `hasPermission` middleware function is again similar to the one we used in the previous examples, where the only difference is the call to the `policy` object:

```js
const hasPermission = (action) => {
  return async (req, res, next) => {
    const { user } = req.body;
    const { asset } = req.params;
    const userRoles = resolveUserRoles(user);

    const allowed = await userRoles.reduce(async (perms, role) => {
      const acc = await perms;
      if (acc) return true;

      const can = await policy.can(role, action, asset);
      if (can) return true;
    }, false);

    allowed ? next() : res.status(403).send("Forbidden").end();
  };
};
```

Click [here](https://github.com/aserto-demo/node-rbac/tree/main/rbac) to view the full RBAC implementation.

## [Access-Control](https://github.com/onury/accesscontrol)

The Access-Control project offers a "Chainable, friendly API" with hierarchical role inheritance. It allows developers to define roles using a single definition file or using a chain of `.can` calls. It only supports the CRUD action verbs, with two ownership modifiers: `any` and `own`.

In this example, we define the roles and permissions in a file called `grantlist.js`:

```js
const grantList = [
  { role: "evilGenius", asset: "megaSeeds", action: "delete:any" },
  { role: "evilGenius", asset: "timeCrystals", action: "delete:any" },
  {
    role: "evilGenius",
    asset: "megaSeeds",
    action: "read:any",
  },
  { role: "editor", asset: "megaSeeds", action: "update:any" },
  { role: "editor", asset: "timeCrystals", action: "update:any" },
  {
    role: "editor",
    asset: "megaSeeds",
    action: "read:any",
    attributes: ["*", "!id"],
  },
  { role: "user", asset: "megaSeeds", action: "read:any" },
  { role: "user", asset: "timeCrystals", action: "read:any" },
];

module.exports = grantList;
```

As in the other examples, we have a mapping between roles, assets, and actions. Unlike the other examples, we are limited to the CRUD actions, and in our case, only `read`, `update`, and `delete` apply. As you'll see below, we mapped our custom actions (`gather`, `consume` and `destroy`) to the CRUD actions (it's a bit odd, but that's what you get when you build your authorization library only around CRUD actions...)

We also specify that the `sidekick` role will be able to `readAny` of the `megaSeeds`, but we also limit the attributes that can be read. Specifically, we allow the `sidekick` to access all the attributes except for the `id` attribute.

We import the grant list to our main application file, and initialize the `AccessControl` object:

```js
const grantList = require("./grantlist");
const ac = new AccessControl(grantList);
```

In this case, instead of explicitly declaring all the roles and permissions, we can `extend` one role with another:

```js
ac.grant("evilGenius").extend("sidekick");
```

The `hasPermission` implementation is a bit different than the other libraries we reviewed so far.

```js
const hasPermission = (action) => {
  return (req, res, next) => {
    const { user } = req.body;
    const { asset } = req.params;
    const userRoles = resolveUserRoles(user);
    const allowed = userRoles.reduce((perms, role) => {
      let permissions;
      switch (action) {
        case "gather":
          permissions = ac.can(role).readAny(asset);
          if (permissions.granted) {
            perms = perms.concat(permissions);
          }
          break;
        case "consume":
          permissions = ac.can(role).updateAny(asset);
          if (permissions.granted) {
            perms = perms.concat(permissions);
          }
          break;
        case "destroy":
          permissions = ac.can(role).deleteAny(asset);
          if (permissions.granted) {
            perms = perms.concat(permissions);
          }
          break;
      }
      return perms;
    }, []);

    if (allowed.length) {
      const result = allowed.map((perm) => {
        const data = assets[asset];
        return {
          data: perm.filter(data),
          asRole: perm._.role,
        };
      });

      res.locals = result;
      next();
    } else {
      res.status(403).send("Forbidden");
    }
  };
};
```

In this code snippet, we `switch` over the `action` based on the CRUD verb associated with it. We then iterate over the `userRoles` array and collect the permissions for each role.

After collecting all the permissions, we iterate over them again and "fetch" any data the user has access to from a mock store (`assets`).

```js
const assets = {
  megaSeeds: {
    id: "megaSeeds",
    content: "This is asset 1",
  },
  timeCrystals: {
    id: "timeCrystals",
    content: "This is asset 2",
  },
};
```

We then use the `perm.filter` method to filter the data such that only the allowed attributes are passed to the route function.

In this example, when we test the `evilGenius` user with the action `gather` on `megaSeeds` we'll get the following result:

```json
[
  {
    "data": {
      "content": "Mega Seeds grow on Mega Trees"
    },
    "asRole": "clone"
  },
  {
    "data": {
      "id": "megaSeeds",
      "content": "Mega Seeds grow on Mega Trees"
    },
    "asRole": "evilGenius"
  }
]
```

Based on the grants definition above, the `clone` is not allowed to see the `id` attribute, but the `evilGenius` is allowed to see all the attributes.

Click [here](https://github.com/aserto-demo/node-rbac/tree/main/accesscontrol) to view the full Access-Control implementation.

## [Aserto](https://aserto.com)

Aserto takes a fundamentally different approach to authorization than all of the examples we've seen above. First and foremost - Aserto is an authorization _service_, with an SDK that allows easy integration into the application. Aserto can be deployed as a sidecar to your application - which guarantees maximum availability as well as a single-digit millisecond response time for authorization decisions.

There are a couple of additional key differences that sets Aserto apart from the other libraries we've reviewed so far.

- **Policy as Code** - What we've seen in the examples so far could be grouped into an approach called "Policy as Data", where the policy itself is reasoned through the data that represents it. Aserto uses a different approach where the policy is expressed and reasoned about as _code_.

Reasoning about the policy as code makes the policy a lot more natural to write and maintained by developers. It takes away the need to traverse and reason about complex graphs or data structures. It also allows for more flexibility in the policy definition, as policies can be defined in a much more declarative way. Instead of convoluted data structures, developers can write the policy in a way that is a lot more concise and readable - and changes to the policy are made by changing the rules of the policy as opposed to rows in a database.

- **Users as First-Class Citizens** - With Aserto, users and their roles are first-class citizens. Aserto provides a directory of users and their roles which is continuously synchronized with the Aserto authorizer. This allows Aserto to reason about users and their roles as part of the policy itself - without requiring role resolution as an additional external step (This is why the `users.json` file or the `resolveUserRoles` function are not going to be required as you'll see below). Having the role resolution as part of the application comes with its own set of risks - and the directory eliminates the risk of contaminating the decision engine with untrustworthy data.

### Setting up Aserto

Aserto offers a console for managing policies - to create a new policy, you'll need to [sign in](https://console.aserto.com). If you don't already have an Aserto account, you can create one [here](https://aserto.com/#signup).

#### Add The Acmecorp IDP

To simulate the behavior of a user directory, we'll add the "Acmecorp IDP", which includes mock users that will be added to our directory. Head on to the Aserto Console, select the "Connections" tab and click the "Add Connection" button.

![add-connection-button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wcyjgao7tg8hcjth9ie.png)

From the drop-down menu, select "Acmecorp"

![add-acmecorp-connections](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cpvto7zapa9wu5ed8vjy.png)

Name the provider `acmecorp` and give it a description.

Finally click “Add connection”:

![add-connection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1mirrcu8q2tqlr6a6bp.png)
 

#### Create a Policy

[Click here](https://console.aserto.com/ui/createpolicy) to create a new policy.

First, select your source code provider. If you haven't set one up already, you can do so by clicking the "Add a new source code connection" in the dropdown. This will bring up a modal for adding a connection to a provider. Note that Aserto supports GitHub as a source code provider, but allows you to connect to it either over an OAuth2 flow, or using a Personal Access Token (PAT).

![connect-github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vk866xyzs9ag2ftt4zz.png)
 
After you're done connecting your Github account (or if you previously connected it), select "github" as your Source code provider.

![select-github-provider](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3q67af0q53qr46dubw12.png)

Next, you'll be asked to select an organization & repo. Select the “New (using template)” radio button, and select the "policy-template" template.

![create-policy-repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ruhyf7nyqmidqjctipmr.png)
 
Name your policy repo "policy-node-rbac" and click "Create repo".

![create-repo-button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thk41u4jw343w2j1i8bs.png)

Name your policy "policy-node-rbac":

![name-policy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r3tsy4icvzbe9mc3ff3x.png)
 
And finally click "Add policy":

![add-policy-button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rkxpvvxb3rhaku1gj7v7.png) 

Head to Github and open the newly created repository, and clone it.

```bash
git clone https://github.com/[your-organization]/policy-node-rbac
```

Lastly, delete the policy `hello.rego` under the `/src/policies` folder.

#### Aserto Policies

Let's take a look at how policies are defined in Aserto. For the use case we presented, we'll need a policy for every route the application exposes. Let's start by creating the policy `/api/read/:asset` route. Under `/src/policies`, we'll create a file called `noderbac.POST.api.read.__asset.rego`, and paste the following code into it:

```
package noderbac.POST.api.__asset

default allowed = false

allowed {
    input.user.attributes.roles[_] == "clone"
    input.resource.asset == data.assets[_]
}

allowed {
    input.user.attributes.roles[_] == "sidekick"
    input.resource.asset == data.assets[_]
}

allowed {
    input.user.attributes.roles[_] == "evilGenius"
    input.resource.asset == data.assets[_]
}
```

The first line of the policy defines the name of the package, and it matches the route it will protect. Next, we define that by default, the `allowed` decision will be `false` - this means we're defaulting to a closed system, where access has to be explicitly granted.

The next three clauses will evaluate the `allowed` decision based on the user's roles and the asset they're trying to access. For example, the first line in the first clause will check if the user has the role of `clone` assigned to them. The user roles are automatically resolved by Aserto based on the user's identity.

The second line of the first clause will check whether the asset the user is trying to access is listed in the `data.assets` object, which is part of the policy. The asset is passed to the policy as part of the resource context (more details below). A policy can have a data file attached that could be used in the context of the policy. In our case, it includes the list of assets users can access. Under the `/src` folder, create a file called `data.json` and paste the following code into it:

```
{
  "assets": ["megaSeeds", "timeCrystals"]
}
```

Using a separate data file to define the protected assets, we don't have to explicitly define them in the policy (as we had to do in the previous examples).

The policies for `/api/edit/:asset` and `/api/delete/:asset` are identical to the ones for `/api/read/:asset`, except that the roles associated with each are different.

We'll create a file under `/src/policies` called `noderbac.PUT.api.__asset.rego` and paste the following code into it:

```
package noderbac.PUT.api.__asset

default allowed = false

allowed {
    input.user.attributes.roles[_] == "sidekick"
    input.resource.asset == data.assets[_]
}

allowed {
    input.user.attributes.roles[_] == "evilGenius"
    input.resource.asset == data.assets[_]
}
```

Next, we'll create a file under `/src/policies` called `noderbac.DELETE.api.__asset.rego` and paste the following code into it:

```
package noderbac.DELETE.api.__asset

default allowed = false

allowed {
    input.user.attributes.roles[_] == "evilGenius"
    input.resource.asset == data.assets[_]
}
```

As you can see, the policy for the `consume` route is allowing both `sidekick` and `evilGenius` access, while the policy for the `destroy` route is allowing access only to `evilGenius`.

Lastly, we'll update the `.manifest` file to include the reference to the data in our `data.json` file. Update the `/src/manifest.json` file to include the following:

```json
{
  "roots": ["noderbac", "assets"]
}
```

To deploy the new policy, we'll just commit, tag, and push it to the repo we created:

```
git add .
git commit -m "Created RBAC Policy"
git push
git tag v0.0.1
git push --tags
```

### Application implementation

The `hasPermission` function implementation is mostly similar, except that we're not going to resolve the user roles, since Aserto will do that for us:

```js
const { is } = require("express-jwt-aserto");

const options = {
  authorizerServiceUrl: "https://authorizer.prod.aserto.com",
  policyId: process.env.POLICY_ID,
  authorizerApiKey: process.env.AUTHORIZER_API_KEY,
  tenantId: process.env.TENANT_ID,
  policyRoot: process.env.POLICY_ROOT,
  useAuthorizationHeader: false,
};

const hasPermission = (action) => {
  return async (req, res, next) => {
    const { user } = req.body;
    const { asset } = req.params;
    req.user = { sub: user.id };
    const allowed = await is("allowed", req, options, false, { asset });
    allowed ? next() : res.status(403).send("Forbidden").end();
  };
};
```

Here we pass the user's `id` as part of the `req` object. In production use cases, the `req.user` object would be populated after the user's authentication has been completed. The `is` function is going to return the `allowed` decision for the given route (encapsulated in the `req` object), for the `asset` we specify in the resource context.

The configuration passed to the `is` function (in the `options` object) requires that we create a `.env` file in the root of the project, and populate some environment variables from the Aserto console, on the Policy Details page:

![policy-details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jop97izk3lpf28jcvgte.png)
 
Copy the Policy ID, Authorizer API Key, and Tenant ID to the `.env` file:

```
POLICY_ID=<Your Policy ID>
AUTHORIZER_API_KEY=<Your Authorizer API Key>
TENANT_ID=<Your Tenant ID>
POLICY_ROOT=noderbac
```

To run the example, run the following commands in the `aserto` directory:

```
yarn install
yarn start
```

Finally, you can test the application by running the same `curl` commands as before:

```
curl --location --request <HTTP Verb> 'http://localhost:8080/api/<asset>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "user": {
        "id": "rick@the-citadel.com"
    }

}'
```

## Summary

In the post, we reviewed multiple ways of adding RBAC to your application. We've seen that in most cases, users are not considered a first-class citizen concept in the authorization offering and that the process of role resolution is left to the developer, and ends up as part of the application itself, which introduces many risks. We've also seen that most solutions take the "Policy-as-Data" approach as opposed to the "Policy-as-Code" approach.

While it might seem easier to use a library to implement RBAC in your Node.JS application, it is important to consider the lifecycle of the application and how it'll grow. How will new users and roles be added? What would be the implications of changing the authorization policy? How will we reason about the authorization policy when it gets to be more complex?

Using a library means that you assume ownership of the authorization component - which requires time and effort to build and maintain. By using a service such as Aserto you can offload the responsibility of managing the authorization flow - without sacrificing the performance or availability of your application.