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

  1. Create a @Roles() decorator that stores required roles as metadata via SetMetadata.
  2. A RolesGuard reads that metadata with the Reflector and compares it to the user's roles.
  3. Apply both the auth guard and the roles guard to the route.

This separates the rule (which roles) from the enforcement (the guard).

Example

Example Β· typescript
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.

Example Β· ts
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.

Example Β· ts
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.

Example Β· ts
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

  • Be the first to comment on this lesson.