typescript 45 lines · 6 steps

Role-based access with a NestJS guard

A custom decorator tags routes with allowed roles and a guard enforces them per request.

Explained by highlit
1import {
2 CanActivate,
3 ExecutionContext,
4 Injectable,
5 SetMetadata,
6 ForbiddenException,
7} from '@nestjs/common';
8import { Reflector } from '@nestjs/core';
9 
10export type AppRole = 'admin' | 'editor' | 'viewer';
11 
12export const ROLES_KEY = 'roles';
13export const Roles = (...roles: AppRole[]) => SetMetadata(ROLES_KEY, roles);
14 
15@Injectable()
16export class RolesGuard implements CanActivate {
17 constructor(private readonly reflector: Reflector) {}
18 
19 canActivate(context: ExecutionContext): boolean {
20 const requiredRoles = this.reflector.getAllAndOverride<AppRole[]>(ROLES_KEY, [
21 context.getHandler(),
22 context.getClass(),
23 ]);
24 
25 if (!requiredRoles?.length) {
26 return true;
27 }
28 
29 const { user } = context.switchToHttp().getRequest();
30 
31 if (!user) {
32 throw new ForbiddenException('Authentication required');
33 }
34 
35 const granted = requiredRoles.some((role) => user.roles?.includes(role));
36 
37 if (!granted) {
38 throw new ForbiddenException(
39 `Requires one of the following roles: ${requiredRoles.join(', ')}`,
40 );
41 }
42 
43 return true;
44 }
45}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Decorators attach metadata that guards can read later at request time via the Reflector.
  2. 2getAllAndOverride lets method-level metadata override class-level defaults cleanly.
  3. 3Returning true from canActivate allows the request while throwing rejects it with a chosen status.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Role-based access with a NestJS guard — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code