A access control library for NestJS which built on node-casbin.
Casbin is a powerful and efficient open-source access control library. It provides support for enforcing authorization based on various access control models like ACL, RBAC, ABAC. For detailed info, check out the official docs
$ npm install --save nest-authz
Firstly, you should create your own casbin access control model. Checkout related docs if you have not.
Register nest-authz with options in the AppModule as follows:
AuthZModule.register(options)
options
is an object literal containing options.
-
model
is a path string to the casbin model. -
policy
is a path string to the casbin policy file or adapter -
usernameFromContext
(REQUIRED) is a function that acceptsExecutionContext
(the param of guard methodcanActivate
) as the only parameter and returns either the username as a string or null. TheAuthZGuard
uses username to determine user's permission internally. -
enforcerProvider
Optional enforcer provider -
imports
Optional list of imported modules that export the providers which are required in this module.
There are two ways to configure enforcer, either enforcerProvider
(optional with imports
) or model
with policy
An example configuration which reads username from the http request.
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
AuthZModule.register({
model: 'model.conf',
policy: TypeORMAdapter.newAdapter({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'nestdb'
}),
usernameFromContext: (ctx) => {
const request = ctx.switchToHttp().getRequest();
return request.user && request.user.username;
}
}),
],
controllers: [AppController],
providers: [AppService]
})
or
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from './config.module';
import { AUTHZ_ENFORCER } from 'nest-authz';
@Module({
imports: [
ConfigModule,
AuthZModule.register({
imports: [ConfigModule],
enforcerProvider: {
provide: AUTHZ_ENFORCER,
useFactory: async (configSrv: ConfigService) => {
const config = await configSrv.getAuthConfig();
return casbin.newEnforcer(config.model, config.policy);
},
inject: [ConfigService],
},
usernameFromContext: (ctx) => {
const request = ctx.switchToHttp().getRequest();
return request.user && request.user.username;
}
}),
],
controllers: [AppController],
providers: [AppService]
The latter one is preferred.
The @UserPermissions
decorator is the easiest and most common way of checking permissions. Consider the method shown below:
@Get('users')
@UseGuards(AuthZGuard)
@UsePermissions({
action: AuthActionVerb.READ,
resource: 'USER',
possession: AuthPossession.ANY
})
async findAllUsers() {}
The findAllUsers
method can not be called by a user who is not granted the permission to read any user.
The value of property resource
is a magic string just for demonstrating. In the real-world applications you should avoid magic strings. Resources should be kept in the separated file like resources.ts
The param of UsePermissions
are some objects with required properties action
、 resource
、 possession
and an optional isOwn
.
-
action
is an enum value ofAuthActionVerb
. -
resource
is a resource string the request is accessing. -
possession
is an enum value ofAuthPossession
. -
isOwn
is a function that acceptsExecutionContext
(the param of guard methodcanActivate
) as the only parameter and returns boolean. TheAuthZGuard
uses it to determine whether the user is the owner of the resource. A defaultisOwn
function which returnsfalse
will be used if not defined.
You can define multiple permissions, but only when all of them satisfied, could you access the route. For example:
@UsePermissions({
action: AuthActionVerb.READ,
resource: 'USER_ADDRESS',
possession: AuthPossession.ANY
}, {
action; AuthActionVerb.READ,
resource: 'USER_ROLES,
possession: AuthPossession.ANY
})
Only when the user is granted both permissions of reading any user address and reading any roles, could he/she access the route.
While the @UsePermissions
decorator is good enough for most cases, there are situations where we may want to check for a permission in a method's body. We can inject and use AuthzRBACService
or AuthzManagementService
which are wrappers of casbin api for that as shown in the example below:
import { Controller, Get, UnauthorizedException, Req } from '@nestjs/common';
import {
AuthZGuard,
AuthZRBACService,
AuthActionVerb,
AuthPossession,
UsePermissions
} from 'nest-authz';
@Controller()
export class AppController {
constructor(private readonly rbacSrv: AuthZRBACService) {}
@Get('users')
async findAllUsers(@Req() request: Request) {
let username = request.user['username'];
// If there is a policy `p, root, user, read:any` in policy.csv
// then user `root` can do this operation
// Using string literals for simplicity.
const isPermitted = await this.rbacSrv.hasPermissionForUser(username, "user", "read:any");
if (!isPermitted) {
throw new UnauthorizedException(
'You are not authorized to read users list'
);
}
// A user can not reach this point if he/she is not granted for permission read users
// ...
}
}
For more detailed information, checkout the working example in nest-authz-example
This project is licensed under the MIT license.