We use cookies to enhance your experience on the site
CodeWorlds

Guards in NestJS

Ave, brave legionaries! Consul Caesar.js leads you further into the secrets of NestJS. Today we'll learn about Guards - the highest-class guards of our forum who decide who can access the most valuable resources.

Guards in NestJS are like elite treasury guards - they don't just check identity, they make strategic access decisions based on business logic, user roles, and request context.

What are Guards?

Guards are classes implementing the

CanActivate
interface that determine whether a request can be processed by the handler. They run after middleware but before interceptors and pipes.

1import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
2
3@Injectable()
4export class LegionGuard implements CanActivate {
5 canActivate(context: ExecutionContext): boolean {
6 const request = context.switchToHttp().getRequest();
7 const legionaryRank = request.headers['legionary-rank'];
8
9 // Only legionaries with rank "centurion" or higher
10 return legionaryRank === 'centurion' || legionaryRank === 'tribune';
11 }
12}

Authentication Guard

The most important guard - checks whether the legionary is a member of the legion:

1import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
2import { JwtService } from '@nestjs/jwt';
3
4@Injectable()
5export class AuthGuard implements CanActivate {
6 constructor(private jwtService: JwtService) {}
7
8 canActivate(context: ExecutionContext): boolean {
9 const request = context.switchToHttp().getRequest();
10 const token = this.extractTokenFromHeader(request);
11
12 if (!token) {
13 throw new UnauthorizedException('No token! Access only for legion members!');
14 }
15
16 try {
17 const payload = this.jwtService.verify(token);
18 request['legionary'] = payload;
19 return true;
20 } catch {
21 throw new UnauthorizedException('Invalid token! Are you sure you are one of us?');
22 }
23 }
24
25 private extractTokenFromHeader(request: any): string | undefined {
26 const [type, token] = request.headers.authorization?.split(' ') ?? [];
27 return type === 'Bearer' ? token : undefined;
28 }
29}

Role-Based Access Control (RBAC)

The rank and permission system in the Roman legion:

1// decorators/roles.decorator.ts
2import { SetMetadata } from '@nestjs/common';
3
4export enum LegionaryRank {
5 MILES = 'miles',
6 TESSERARIUS = 'tesserarius',
7 OPTIO = 'optio',
8 CENTURION = 'centurion',
9 TRIBUNE = 'tribune'
10}
11
12export const Ranks = (...ranks: LegionaryRank[]) => SetMetadata('ranks', ranks);
13
14// guards/roles.guard.ts
15import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
16import { Reflector } from '@nestjs/core';
17
18@Injectable()
19export class RolesGuard implements CanActivate {
20 constructor(private reflector: Reflector) {}
21
22 canActivate(context: ExecutionContext): boolean {
23 const requiredRanks = this.reflector.getAllAndOverride<LegionaryRank[]>('ranks', [
24 context.getHandler(),
25 context.getClass(),
26 ]);
27
28 if (!requiredRanks) {
29 return true; // No required ranks = access for everyone
30 }
31
32 const request = context.switchToHttp().getRequest();
33 const legionary = request.legionary;
34
35 if (!legionary) {
36 return false;
37 }
38
39 return requiredRanks.includes(legionary.rank);
40 }
41}
42
43// Usage in controller
44@Controller('tributes')
45export class TributesController {
46 @Get('centurion-only')
47 @Ranks(LegionaryRank.CENTURION, LegionaryRank.TRIBUNE)
48 @UseGuards(AuthGuard, RolesGuard)
49 getCenturionTributes() {
50 return {
51 message: 'Centurion tributes - the most precious gems!',
52 tributes: ['Caesar's Crown', 'Golden Eagle', 'Imperator's Insignia']
53 };
54 }
55}

Custom Authorization Guard

An advanced guard checking various access conditions:

1@Injectable()
2export class TributeAccessGuard implements CanActivate {
3 constructor(
4 private tributeService: TributeService,
5 private legionService: LegionService
6 ) {}
7
8 async canActivate(context: ExecutionContext): Promise<boolean> {
9 const request = context.switchToHttp().getRequest();
10 const legionary = request.legionary;
11 const tributeId = request.params.id;
12
13 // Check if the tribute exists
14 const tribute = await this.tributeService.findById(tributeId);
15 if (!tribute) {
16 throw new NotFoundException('Tribute was not found!');
17 }
18
19 // Different access levels
20 switch (tribute.securityLevel) {
21 case 'public':
22 return true; // Accessible to everyone
23
24 case 'legion':
25 return this.isLegionMember(legionary);
26
27 case 'officers':
28 return this.isOfficer(legionary);
29
30 case 'centurion':
31 return this.isCenturion(legionary);
32
33 case 'personal':
34 return tribute.ownerId === legionary.id;
35
36 default:
37 return false;
38 }
39 }
40
41 private isLegionMember(legionary: any): boolean {
42 return legionary && legionary.status === 'active';
43 }
44
45 private isOfficer(legionary: any): boolean {
46 const officerRanks = [
47 LegionaryRank.OPTIO,
48 LegionaryRank.CENTURION,
49 LegionaryRank.TRIBUNE
50 ];
51 return this.isLegionMember(legionary) && officerRanks.includes(legionary.rank);
52 }
53
54 private isCenturion(legionary: any): boolean {
55 return this.isLegionMember(legionary) &&
56 [LegionaryRank.CENTURION, LegionaryRank.TRIBUNE].includes(legionary.rank);
57 }
58}

Time-Based Access Guard

A guard controlling access depending on time:

1@Injectable()
2export class TimeBasedGuard implements CanActivate {
3 canActivate(context: ExecutionContext): boolean {
4 const now = new Date();
5 const hour = now.getHours();
6 const dayOfWeek = now.getDay();
7
8 const request = context.switchToHttp().getRequest();
9 const route = request.route.path;
10
11 // Treasury accessible only during daytime hours
12 if (route.includes('/vault')) {
13 if (hour < 8 || hour > 18) {
14 throw new ForbiddenException(
15 'Treasury closed! Available only from 8:00 to 18:00'
16 );
17 }
18 }
19
20 // Baths closed on Sundays
21 if (route.includes('/thermae') && dayOfWeek === 0) {
22 throw new ForbiddenException(
23 'Baths closed on Sundays! Even legionaries need rest!'
24 );
25 }
26
27 // Night watch - restricted access after 22:00
28 if (hour >= 22 || hour <= 6) {
29 const legionary = request.legionary;
30 if (!legionary.nightShiftAccess) {
31 throw new ForbiddenException(
32 'Night access only for the night watch!'
33 );
34 }
35 }
36
37 return true;
38 }
39}

IP Whitelist Guard

Access control based on IP address:

1@Injectable()
2export class IpWhitelistGuard implements CanActivate {
3 private readonly allowedIps = [
4 '192.168.1.100', // Consul's computer
5 '192.168.1.101', // Centurion's computer
6 '10.0.0.50', // Engineering server
7 '127.0.0.1' // Localhost for development
8 ];
9
10 canActivate(context: ExecutionContext): boolean {
11 const request = context.switchToHttp().getRequest();
12 const clientIp = this.getClientIp(request);
13
14 if (!this.allowedIps.includes(clientIp)) {
15 console.log(`⚠️ Access attempt from unauthorized IP: ${clientIp}`);
16 throw new ForbiddenException(
17 'Access from this province is not allowed! Only trusted locations can connect!'
18 );
19 }
20
21 return true;
22 }
23
24 private getClientIp(request: any): string {
25 return request.headers['x-forwarded-for'] ||
26 request.connection.remoteAddress ||
27 request.socket.remoteAddress ||
28 request.ip;
29 }
30}

Feature Flag Guard

Access control for new features:

1@Injectable()
2export class FeatureFlagGuard implements CanActivate {
3 constructor(private configService: ConfigService) {}
4
5 canActivate(context: ExecutionContext): boolean {
6 const handler = context.getHandler();
7 const featureFlag = this.reflector.get<string>('feature', handler);
8
9 if (!featureFlag) {
10 return true; // No flag = unrestricted access
11 }
12
13 const isEnabled = this.configService.get<boolean>(`features.${featureFlag}`);
14
15 if (!isEnabled) {
16 throw new ForbiddenException(
17 `Feature '${featureFlag}' is currently unavailable. Engineers are still working on it!`
18 );
19 }
20
21 return true;
22 }
23}
24
25// Decorator for feature flags
26export const Feature = (flag: string) => SetMetadata('feature', flag);
27
28// Usage
29@Controller('experimental')
30export class ExperimentalController {
31 @Get('new-weapon')
32 @Feature('advanced-ballistae')
33 @UseGuards(FeatureFlagGuard)
34 getNewWeapon() {
35 return {
36 name: 'Quantum Ballista',
37 damage: 9999,
38 status: 'Experimental'
39 };
40 }
41}

Rate Limiting Guard

A guard controlling request frequency:

1@Injectable()
2export class RateLimitGuard implements CanActivate {
3 private requests = new Map<string, { count: number; resetTime: number }>();
4
5 constructor(
6 @Inject('RATE_LIMIT_OPTIONS')
7 private options: { max: number; windowMs: number }
8 ) {}
9
10 canActivate(context: ExecutionContext): boolean {
11 const request = context.switchToHttp().getRequest();
12 const key = this.generateKey(request);
13 const now = Date.now();
14
15 let userRequests = this.requests.get(key);
16
17 if (!userRequests || now > userRequests.resetTime) {
18 userRequests = {
19 count: 0,
20 resetTime: now + this.options.windowMs
21 };
22 }
23
24 userRequests.count++;
25 this.requests.set(key, userRequests);
26
27 if (userRequests.count > this.options.max) {
28 throw new TooManyRequestsException(
29 'Too many requests! The forum cannot withstand such an assault!'
30 );
31 }
32
33 return true;
34 }
35
36 private generateKey(request: any): string {
37 const legionary = request.legionary;
38 return legionary ? `legionary-${legionary.id}` : `ip-${request.ip}`
1 }
2}

Composite Guard

A guard combining multiple conditions:

1@Injectable()
2export class TributeVaultGuard implements CanActivate {
3 constructor(
4 private tributeService: TributeService,
5 private auditService: AuditService
6 ) {}
7
8 async canActivate(context: ExecutionContext): Promise<boolean> {
9 const request = context.switchToHttp().getRequest();
10 const legionary = request.legionary;
11 const tributeId = request.params.id;
12
13 // Condition 1: Check legionary rank
14 const hasRequiredRank = this.checkRank(legionary);
15 if (!hasRequiredRank) {
16 this.auditService.logUnauthorizedAccess(legionary.id, 'insufficient_rank');
17 return false;
18 }
19
20 // Condition 2: Check if legionary has active access
21 const hasActiveAccess = await this.checkActiveAccess(legionary);
22 if (!hasActiveAccess) {
23 this.auditService.logUnauthorizedAccess(legionary.id, 'inactive_access');
24 return false;
25 }
26
27 // Condition 3: Check daily limit
28 const withinDailyLimit = await this.checkDailyLimit(legionary);
29 if (!withinDailyLimit) {
30 this.auditService.logUnauthorizedAccess(legionary.id, 'daily_limit_exceeded');
31 throw new ForbiddenException(
32 'Daily tribute access limit exceeded!'
33 );
34 }
35
36 // Condition 4: Check if tribute is currently in use
37 const tributeAvailable = await this.checkTributeAvailability(tributeId);
38 if (!tributeAvailable) {
39 throw new ConflictException(
40 'Tribute is currently being used by another legionary!'
41 );
42 }
43
44 // Log successful access
45 this.auditService.logSuccessfulAccess(legionary.id, tributeId);
46
47 return true;
48 }
49
50 private checkRank(legionary: any): boolean {
51 return [LegionaryRank.OPTIO, LegionaryRank.CENTURION, LegionaryRank.TRIBUNE]
52 .includes(legionary.rank);
53 }
54
55 private async checkActiveAccess(legionary: any): Promise<boolean> {
56 // Check in the database if the legionary has active access
57 return legionary.status === 'active' && !legionary.suspended;
58 }
59
60 private async checkDailyLimit(legionary: any): Promise<boolean> {
61 const today = new Date().toDateString();
62 const accessCount = await this.auditService.getDailyAccessCount(legionary.id, today);
63 return accessCount < 5; // Maximum 5 accesses per day
64 }
65
66 private async checkTributeAvailability(tributeId: string): Promise<boolean> {
67 return await this.tributeService.isAvailable(tributeId);
68 }
69}

Global Guard

1// main.ts
2import { NestFactory } from '@nestjs/core';
3import { AppModule } from './app.module';
4
5async function bootstrap() {
6 const app = await NestFactory.create(AppModule);
7
8 // Global guard for the entire application
9 app.useGlobalGuards(new AuthGuard());
10
11 await app.listen(3000);
12}
13bootstrap();
14
15// Or in a module
16@Module({
17 providers: [
18 {
19 provide: APP_GUARD,
20 useClass: AuthGuard,
21 },
22 ],
23})
24export class AppModule {}
Go to CodeWorlds