In this article, i will show you how to create Role based authorization middleware with Casbin and Nest.js. Casbin is an authorization library that supports access control models like ACL, RBAC, ABAC for Golang, Java, PHP and Node.js, but in this article, we will use the Node.js.

Nest is a progressive Node.js framework for building efficient, reliable and scalable server-side applications. It uses Typescript language and support multiple core server like Express, Koa or Fastify.

If you prefer to watch in video format:

Casbin Midleware for Nest.js with Restful Policy in video format

Here is the text format:


Before we create the project, install Nest first.

npm i -g @nestjs/cli

And then we create a project with the Nest cli

nest new theproject

Then, open the project directory and install the Casbin

cd theproject
npm install --save casbin

After this, we enter the coding phase.

First, create the policy configuration for Casbin.

In the src, create a directory named casbin_conf.

cd src
mkdir casbin_conf

Create model.conf and policy.csv in the casbin_conf

File model.conf:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

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

[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")

File policy.csv:

p, admin, /*, GET
p, notadmin, /, POST

In the policy we define two rules, one is for user who have admin role and the other is for user who have notadmin role.

After that, we create the Nest middleware.

Create a middleware named authorization.middleware.ts in src

File authorization.middleware.ts:

import { Enforcer } from 'casbin';

export class BasicAuthorizer {
    private req: any;
    private enforcer: any;

    constructor(req, enforcer) {
        this.req = req;
        this.enforcer = enforcer;
    }

    getUserRole() {
        const { user } = this.req;
        const { role } = user;
        return role;
    }

    checkPermission() {
        const { req, enforcer } = this;
        const { originalUrl: path, method } = req;
        const userRole = this.getUserRole();
        return enforcer.enforce(userRole, path, method);
    }
}

// the authorizer middleware
export function authz(newEnforcer: () => Promise<Enforcer>) {
    return async (req, res, next) => {
        const enforcer = await newEnforcer();

        // user sample
        req.user = {role: 'admin'};

        if(!(enforcer instanceof Enforcer)) {
            res.status(500).json({500: 'Invalid enforcer'});
            return;
        }

        const authorizer = new BasicAuthorizer(req, enforcer);
        if(!authorizer.checkPermission()) {
            res.status(403).json({403: 'Forbidden'});
            return;
        }

        next();
    }
};

That's the authorization middleware logic, Casbin will help checking the permission.

Then we integrate the middleware in Nest project bootstrap.

Find the maint.ts, integrate the authorization middleware in the app:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

import { newEnforcer } from 'casbin';
import { authz } from './authorization.middleware';

import { join } from 'path';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // authorization middleware that is registered as global middleware
  app.use(authz(async() => {
      const enforcer = await newEnforcer(join(__dirname, 'casbin_conf/model.conf'), join(__dirname, 'casbin_conf/policy.csv'));
      return enforcer;
  }));
  await app.listen(3000);
}
bootstrap();

That is the authorization middleware that is registered as global middleware in the app.

Boot up the project to test:

npm run start:dev

If you want to experiment, modify the sample user in the authz, remove that if you want make it into production.

Here is the project repository.