How to implement Role-Based Access Control (RBAC) in NestJS?
RBAC in NestJS
Role-Based Access Control (RBAC) restricts access to resources based on user roles. NestJS provides Guards and decorators to implement flexible authorization.
1. Define Roles
typescript
export enum Role {
User = 'user',
Admin = 'admin',
Moderator = 'moderator',
SuperAdmin = 'superadmin',
}2. Roles Decorator
typescript
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);3. Roles Guard
typescript
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}4. Apply Guards
typescript
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
@Get('dashboard')
@Roles(Role.Admin, Role.SuperAdmin)
getDashboard() {
return { message: 'Admin dashboard' };
}
@Delete('users/:id')
@Roles(Role.SuperAdmin)
deleteUser(@Param('id') id: string) {
return this.usersService.delete(id);
}
@Get('stats')
@Roles(Role.Admin, Role.Moderator)
getStats() {
return this.statsService.getAll();
}
}5. Global Guard Setup
typescript
@Module({
providers: [
{ provide: APP_GUARD, useClass: JwtAuthGuard },
{ provide: APP_GUARD, useClass: RolesGuard },
],
})
export class AppModule {}Advanced: Permission-Based Access
For finer control, use permissions instead of (or alongside) roles:
typescript
export enum Permission {
ReadUsers = 'read:users',
WriteUsers = 'write:users',
DeleteUsers = 'delete:users',
ReadPosts = 'read:posts',
WritePosts = 'write:posts',
ManageSettings = 'manage:settings',
}
const rolePermissions: Record<Role, Permission[]> = {
[Role.User]: [Permission.ReadUsers, Permission.ReadPosts],
[Role.Moderator]: [
Permission.ReadUsers, Permission.ReadPosts, Permission.WritePosts,
],
[Role.Admin]: [
Permission.ReadUsers, Permission.WriteUsers,
Permission.ReadPosts, Permission.WritePosts,
Permission.ManageSettings,
],
[Role.SuperAdmin]: Object.values(Permission), // All permissions
};typescript
export const RequirePermissions = (...permissions: Permission[]) =>
SetMetadata('permissions', permissions);
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const required = this.reflector.getAllAndOverride<Permission[]>(
'permissions',
[context.getHandler(), context.getClass()],
);
if (!required) return true;
const { user } = context.switchToHttp().getRequest();
const userPermissions = user.roles
.flatMap((role: Role) => rolePermissions[role] || []);
return required.every((perm) => userPermissions.includes(perm));
}
}Usage:
typescript
@Controller('settings')
export class SettingsController {
@Get()
@RequirePermissions(Permission.ManageSettings)
getSettings() {
return this.settingsService.getAll();
}
}Resource Ownership Check
typescript
@Injectable()
export class OwnershipGuard implements CanActivate {
constructor(private readonly postsService: PostsService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user;
const postId = request.params.id;
if (user.roles.includes(Role.Admin)) return true;
const post = await this.postsService.findOne(postId);
return post?.authorId === user.id;
}
}
@Controller('posts')
export class PostsController {
@Put(':id')
@UseGuards(OwnershipGuard)
update(@Param('id') id: string, @Body() dto: UpdatePostDto) {
return this.postsService.update(id, dto);
}
}RBAC vs ABAC
| Feature | RBAC | ABAC |
|---|---|---|
| Based on | Roles | Attributes (role, time, IP, etc.) |
| Complexity | Simple | Complex |
| Flexibility | Moderate | Very high |
| Best for | Most web apps | Enterprise, compliance |
Best practice: Start with simple RBAC. Add permission-based checks when role granularity isn't enough. Add ownership guards for user-owned resources. Use
@Public()for open endpoints.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.