Careeroza
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}