Custom Role-Based Access Control in NestJS Using Custom Guards
Published on Nov 11, 2024 | Reading time: 3 min
This technical walkthrough demonstrates the implementation of a production-ready Role-Based Access Control (RBAC) system in NestJS, leveraging custom guards and metadata reflection to enforce granular endpoint security. The architecture adheres to modular design principles while maintaining compatibility with NestJS’s authentication ecosystem.
Architectural Components
- Role Enumeration Definition
Establishes standardized application roles with type safety:
export enum ApplicationUserRoleEnum {
ADMIN = 'ADMIN',
OWNER = 'OWNER',
USER = 'USER',
}
- Role Requirement Decorator
Enables declarative endpoint security configuration:
import { SetMetadata } from '@nestjs/common';
import { ApplicationUserRoleEnum } from '../enum/application-user-role.enum';
export const RequiredRoles = (...roles: ApplicationUserRoleEnum[]) => SetMetadata('roles', roles);
- Access Control Guard
Implements request interception logic with contextual authorization:
import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
Logger,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { RequiredRoles } from '../decorator/roles.decorator';
import { ApplicationUserRoleEnum } from '../enum/application-user-role.enum';
@Injectable()
export class ApplicationUserRolesGuard implements CanActivate {
private readonly logger = new Logger(ApplicationUserRolesGuard.name);
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const roles = this.reflector.get<ApplicationUserRoleEnum[]>(
RequiredRoles,
context.getHandler(),
);
if (!roles || roles.length === 0) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
if (
!user ||
!roles.some((role) => user.role === role)
) {
this.logger.error(`Unauthorized role: required roles are ${roles.join(', ')}`);
throw new ForbiddenException('You do not have access to perform this action');
}
return true;
}
}
System Integration
1. Global Guard Registration
Enables application-wide security policy enforcement:
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { ApplicationUserRolesGuard } from './guards/application-user-roles.guard';
import { ApplicationUserController } from './controllers/application-user.controller';
import { ApplicationUserService } from './services/application-user.service';
@Module({
controllers: [ApplicationUserController],
providers: [
{
provide: APP_GUARD,
useClass: ApplicationUserRolesGuard,
},
ApplicationUserService,
],
})
export class ApplicationUserModule {}
2. Endpoint Security Configuration
Demonstrates tiered access control implementation:
import { Controller, Get, Patch, Param, Delete, Body } from '@nestjs/common';
import { RequiredRoles } from '../decorator/roles.decorator';
import { ApplicationUserRoleEnum } from '../enum/application-user-role.enum';
import { UpdateUserDto } from '../dto/update-user.dto';
@Controller('users')
export class ApplicationUserController {
@Get(':id')
findOne(@Param('id') id: string) {
return `User ${id} details`;
}
@Patch(':id')
@RequiredRoles(ApplicationUserRoleEnum.ADMIN, ApplicationUserRoleEnum.OWNER)
update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
) {
return `Updated user ${id} with data ${JSON.stringify(updateUserDto)}`;
}
@Delete(':id')
@RequiredRoles(ApplicationUserRoleEnum.ADMIN)
remove(@Param('id') id: string) {
return `Deleted user ${id}`;
}
}
Security Workflow Analysis
-
Request Interception
- Guard intercepts incoming HTTP requests
- Extracts role requirements from endpoint metadata
-
Authorization Validation
- Compares authenticated user’s roles with endpoint requirements
- Implements logical OR permission evaluation
-
Access Decision
- Grants access for matching role profiles
- Throws
ForbiddenException
for unauthorized requests
-
Audit Logging
- Records authorization failures with required role context
- Maintains security incident trail
Production Considerations
-
Performance Optimization
- Metadata caching for rapid role requirement retrieval
- Lean validation logic for minimal request overhead
-
Security Enhancements
- JWT claim validation integration
- Session management compatibility
-
Extensibility Features
- Dynamic role configuration support
- Multi-factor authentication integration points
-
Monitoring Capabilities
- Metrics collection for authorization attempts
- Alerting mechanisms for repeated failures
Enterprise Implementation Patterns
-
Hierarchical RBAC
- Implement role inheritance structures
- Develop tiered permission escalation protocols
-
Attribute-Based Control
- Combine role and resource attributes
- Implement contextual access policies
-
Distributed Systems Support
- Microservices permission propagation
- Cross-service role validation
This implementation establishes a robust foundation for enterprise authorization requirements, offering:
- Declarative Security Configuration: Simplified endpoint protection through decorators
- Centralized Policy Management: Unified guard implementation across modules
- Audit-Compliant Logging: Detailed authorization attempt records
- Scalable Architecture: Support for complex organizational structures
The pattern serves as a critical component in modern application security stacks, providing granular access control while maintaining developer productivity and system performance. Future enhancements could incorporate time-based restrictions or geolocation validation for heightened security postures.