Roles Guard with Reflector

A Custom @Roles Decorator

Attach the required roles to a handler as metadata, then read it in the guard with Reflector.

src/auth/roles.decorator.ts

import { SetMetadata } from '@nestjs/common';

export enum Role { Customer = 'customer', Seller = 'seller', Admin = 'admin' }
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);

// NestJS 10.2+ alternative with built-in typing:
// export const Roles = Reflector.createDecorator<Role[]>();

src/auth/roles.guard.ts

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role, ROLES_KEY } from './roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const required = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
      context.getHandler(),       // method metadata wins...
      context.getClass(),         // ...over class metadata
    ]);
    if (!required || required.length === 0) return true;     // no @Roles: open to any logged-in user
    const { user } = context.switchToHttp().getRequest();
    return required.some((role) => user?.roles?.includes(role));
  }
}

Protecting Routes

src/products/products.controller.ts

@Controller('products')
@UseGuards(JwtAuthGuard, RolesGuard)       // authenticate first, then check roles
export class ProductsController {
  @Get()
  findAll() {}                              // any logged-in user

  @Post()
  @Roles(Role.Admin, Role.Seller)
  create(@Body() dto: CreateProductDto) {}

  @Delete(':id')
  @Roles(Role.Admin)
  remove(@Param('id', ParseIntPipe) id: number) {}
}

Example

A customer token trying to delete:

curl -i -X DELETE http://localhost:3000/products/7 -H "Authorization: Bearer <customer-token>"

Example output

HTTP/1.1 403 Forbidden
{"message":"Forbidden resource","error":"Forbidden","statusCode":403}