Roles & Authorization
Combine a custom guard with metadata to allow only certain roles onto a route.
Syntax
@Roles('admin') / @UseGuards(RolesGuard)Authentication asks who are you; authorization asks are you allowed. Role-based access control uses metadata plus a guard.
The pattern
- Create a
@Roles()decorator that stores required roles as metadata viaSetMetadata. - A
RolesGuardreads that metadata with theReflectorand compares it to the user's roles. - Apply both the auth guard and the roles guard to the route.
This separates the rule (which roles) from the enforcement (the guard).
Example
import { SetMetadata } from '@nestjs/common';
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const required = this.reflector.get<string[]>('roles', context.getHandler());
if (!required) return true;
const { user } = context.switchToHttp().getRequest();
return required.some((r) => user.roles?.includes(r));
}
}When to use it
- A developer decorates an admin endpoint with @Roles("admin") and configures RolesGuard to read that metadata and reject users without the admin role.
- A team stores roles as an array on the user JWT payload so the guard can check them without an extra database call per request.
- An engineer combines the JWT guard (authentication) and RolesGuard (authorization) as sequential guards on the same controller.
More examples
@Roles decorator and metadata
Creates a @Roles decorator that attaches a list of allowed role strings as metadata on a route handler.
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
// Usage:
@Roles('admin', 'manager')
@Get('reports')
getReports() { return []; }RolesGuard reading metadata
Reads the @Roles metadata from the handler or class and checks whether the authenticated user has at least one of the required roles.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(ctx: ExecutionContext): boolean {
const required = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
ctx.getHandler(), ctx.getClass(),
]);
if (!required) return true;
const { user } = ctx.switchToHttp().getRequest();
return required.some(r => user?.roles?.includes(r));
}
}Combining JWT and Roles guards
Stacks JwtAuthGuard and RolesGuard so a request must first carry a valid token and then possess the admin role.
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard) // JWT runs first, then roles
export class AdminController {
@Get('dashboard')
@Roles('admin')
getDashboard() { return 'admin only'; }
}
Discussion