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.