Skip to main content
👀 Interested in the latest enterprise backend features of refine? 👉 Join now and get early access!
Build low-code, customizable and authorization ready (accesscontrol) admin panel with NestJS.
Software Developer
4 min read

Build low-code, customizable and authorization ready (accesscontrol) admin panel with NestJS.

In the previous blog post, we used nestjs with an api and refine in the admin panel. In this blog, let's add authorization to both api and admin panel.

All the steps described are in this repo.

Intro

In the previous blog post, we used nestjs with an api and refine in the admin panel. In this blog, let's add authorization to both api and admin panel.

Scenario

Let's have two roles in this system, they are admin and editor. In the API we prepared, we had two crud processes that we categorized as companies and jobs.

In this scenario; editor can only list companies, not any deletion or additions. Have the authority to list and create job postings. Let admin have authorization for all transactions.

Authorization

I used nestjsx-crud in the api we prepared. This library makes CRUD operations very easy. However, there is no support on the authorization side. That's why I made use of the accesscontrol library, which can be easily integrated with both nestjs and refine.

Using AccessControl in API

In the first step, let's install nestjs-access-control in our project for accesscontrol integration to the api.

npm install nest-access-control

I'm specifying a role as the AccessControl supports. According to our scenario, this should be as follows:

// app.roles.ts

import { RolesBuilder } from 'nest-access-control';

export enum AppRoles {
ADMIN = 'ADMIN',
EDITOR = 'EDITOR',
}

export const roles: RolesBuilder = new RolesBuilder();

roles
// editor
.grant(AppRoles.EDITOR)
.create('jobs')
.update('jobs')
// admin
.grant(AppRoles.ADMIN)
.extend(AppRoles.EDITOR)
.create(['companies'])
.update(['companies'])
.delete(['companies', 'jobs']);

Now I import AccessControlModule.

  // app.module.ts

import { roles } from './app.roles';

@Module({
imports: [
...
AccessControlModule.forRoles(roles)
],
controllers: [...],
providers: [...],
})
export class AppModule {}

After determining the roles and privileges, we add the ACGuard class to the controller UseGuards.

import { ACGuard } from 'nest-access-control';

...
@UseGuards(JwtAuthGuard, ACGuard)
@Controller('companies')
export class CompanyController implements CrudController<CompanyEntity> {}
...

Now we define resource and action for methods using UseRoles decorator. For example, we override for the companies resource and the create action as follows.

import { ACGuard, UseRoles } from 'nest-access-control';

...
@UseGuards(JwtAuthGuard, ACGuard)
@Controller('companies')
export class CompanyController implements CrudController<CompanyEntity> {
constructor(public service: CompanyService) {}

get base(): CrudController<CompanyEntity> {
return this;
}

@Override()
@UseRoles({
resource: 'companies',
action: 'create',
})
createOne(
@ParsedRequest() req: CrudRequest,
@ParsedBody() dto: CompanyCreateDto,
) {
return this.base.createOneBase(req, <CompanyEntity>dto);
}
...

Similarly, we add this decorator for other methods.

After these operations, we complete the authorization process on the API side. Now we will do the authorization to the admin panel that we created with refine.

Using AccessControl in refine (dashboard)

refine; It supports many authorization tools, very flexible. What we need to do; Defining an accessControlProvider inside the <Refine /> component.

accessControlProvider is implemented only one asynchronous method named "can" to be used to control whether the requested access is granted. This method takes resource and action with parameters.

// App.tsx

<Refine
...
accessControlProvider={{
can: async ({ resource, action }) => {
let can: boolean = false;
const stringifyUser = localStorage.getItem('refine-user');
if (stringifyUser) {
const { roles } = JSON.parse(stringifyUser);

roles.forEach((role: string) => {
switch (action) {
case 'list':
case 'show':
can = ac.can(role).read(resource).granted;
break;
case 'create':
can = ac.can(role).create(resource).granted;
break;
case 'edit':
can = ac.can(role).update(resource).granted;
break;
case 'delete':
can = ac.can(role).delete(resource).granted;
break;
}
});
}
return Promise.resolve({ can });
},
}}
/>****

Now let me explain a little bit of this code I wrote. First we need the role of the logged in user. We saved it to local storage during login. Then we match the refine actions with the accessControl's actions and check its authorization with the granted method. I also resolve the returned result.

Conclusion

As a result, we have done the authorization on both the ui (dashboard) side and the api side.

Related Articles

Building a React Admin Panel with PrimeReact and refine

We'll build a simple React admin Panel using refine and PrimeReact.

Adding PDF Renderer

We are going to add PDF renderer to our PDF Invoice Generator app that we have been building last few days. This is also where we summarize our accomplishments and wrap up the series.

NextAuth - Google And GitHub Authentications for Nextjs

How to implement Google and GitHub authentications using NextAuth.js in Next.js?