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

  1. Role Enumeration Definition
    Establishes standardized application roles with type safety:
export enum ApplicationUserRoleEnum {
  ADMIN = 'ADMIN',
  OWNER = 'OWNER',
  USER = 'USER',
}
  1. 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);
  1. 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

  1. Request Interception

    • Guard intercepts incoming HTTP requests
    • Extracts role requirements from endpoint metadata
  2. Authorization Validation

    • Compares authenticated user’s roles with endpoint requirements
    • Implements logical OR permission evaluation
  3. Access Decision

    • Grants access for matching role profiles
    • Throws ForbiddenException for unauthorized requests
  4. Audit Logging

    • Records authorization failures with required role context
    • Maintains security incident trail

Production Considerations

  1. Performance Optimization

    • Metadata caching for rapid role requirement retrieval
    • Lean validation logic for minimal request overhead
  2. Security Enhancements

    • JWT claim validation integration
    • Session management compatibility
  3. Extensibility Features

    • Dynamic role configuration support
    • Multi-factor authentication integration points
  4. Monitoring Capabilities

    • Metrics collection for authorization attempts
    • Alerting mechanisms for repeated failures

Enterprise Implementation Patterns

  1. Hierarchical RBAC

    • Implement role inheritance structures
    • Develop tiered permission escalation protocols
  2. Attribute-Based Control

    • Combine role and resource attributes
    • Implement contextual access policies
  3. Distributed Systems Support

    • Microservices permission propagation
    • Cross-service role validation

This implementation establishes a robust foundation for enterprise authorization requirements, offering:

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.