guards
SKILL.md
NestJS Guards
When to Use This Skill
Use this skill when:
- Implementing authentication (verifying user identity)
- Implementing authorization (checking user permissions/roles)
- Protecting routes based on user roles or permissions
- Validating JWT tokens or API keys
- Implementing custom access control logic
- Restricting access based on request context
- Building role-based access control (RBAC) systems
- Implementing feature flags or conditional route access
What are Guards?
Guards determine whether a request will be handled by the route handler. They implement the CanActivate interface and return a boolean value (or Promise/Observable) indicating whether access is allowed.
Guards execute after middleware but before interceptors and pipes in the request lifecycle.
Basic Guard
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
function validateRequest(request: any): boolean {
return true; // Replace with actual validation logic
}
Key Points:
- Decorated with
@Injectable() - Implements
CanActivateinterface canActivate()returns boolean (true = allow, false = deny)- Has access to
ExecutionContextfor detailed request information - Throws
ForbiddenExceptionwhen returning false
Execution Context
ExecutionContext extends ArgumentsHost and provides additional context about the current execution:
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// Get HTTP-specific context
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
// Get handler and class references
const handler = context.getHandler();
const controller = context.getClass();
return true;
}
}
Binding Guards
Method-scoped Guard
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller('cats')
export class CatsController {
@Get()
@UseGuards(AuthGuard)
findAll() {
return 'This route is protected';
}
}
Controller-scoped Guard
import { Controller, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller('cats')
@UseGuards(AuthGuard)
export class CatsController {
// All routes in this controller are protected
@Get()
findAll() {
return 'Protected route';
}
}
Global Guard
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}
Alternative (in main.ts):
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new AuthGuard());
Role-based Authorization Guard
Setting Roles with Custom Decorator
// roles.decorator.ts
import { SetMetadata } from '@nestjs/core';
export enum Role {
User = 'user',
Admin = 'admin',
Moderator = 'moderator',
}
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
Using Roles Decorator
import { Controller, Get } from '@nestjs/common';
import { Roles, Role } from './roles.decorator';
@Controller('cats')
export class CatsController {
@Get()
@Roles(Role.Admin)
findAll() {
return 'Only admins can access this';
}
}
Roles Guard Implementation
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY, Role } from './roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true; // No roles required, allow access
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
JWT Authentication Guard
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('No token provided');
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: process.env.JWT_SECRET,
});
request['user'] = payload;
} catch {
throw new UnauthorizedException('Invalid token');
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
API Key Guard
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(private configService: ConfigService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const apiKey = request.headers['x-api-key'];
const validApiKey = this.configService.get('API_KEY');
if (!apiKey || apiKey !== validApiKey) {
throw new UnauthorizedException('Invalid API key');
}
return true;
}
}
Combining Multiple Guards
@Controller('cats')
@UseGuards(AuthGuard, RolesGuard)
export class CatsController {
@Get()
@Roles(Role.Admin)
findAll() {
return 'Protected by both guards';
}
}
Guards execute in the order they are applied (left to right).
Public Routes (Skipping Guards)
Public Decorator
// public.decorator.ts
import { SetMetadata } from '@nestjs/core';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
Modified Auth Guard
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from './public.decorator';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
// Perform authentication check
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
Using Public Decorator
@Controller('auth')
export class AuthController {
@Public()
@Post('login')
login() {
return 'This route is public';
}
}
Permission-based Guard
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
export enum Permission {
CreateCat = 'create:cat',
ReadCat = 'read:cat',
UpdateCat = 'update:cat',
DeleteCat = 'delete:cat',
}
export const PERMISSIONS_KEY = 'permissions';
export const RequirePermissions = (...permissions: Permission[]) =>
SetMetadata(PERMISSIONS_KEY, permissions);
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(
PERMISSIONS_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredPermissions) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredPermissions.every((permission) =>
user.permissions?.includes(permission),
);
}
}
// Usage
@Controller('cats')
export class CatsController {
@Get()
@RequirePermissions(Permission.ReadCat)
findAll() {
return 'Requires read permission';
}
@Delete(':id')
@RequirePermissions(Permission.DeleteCat)
remove() {
return 'Requires delete permission';
}
}
Reflector Service
The Reflector class is used to retrieve metadata set by decorators:
constructor(private reflector: Reflector) {}
// Get metadata from handler only
const roles = this.reflector.get<string[]>('roles', context.getHandler());
// Get metadata from class only
const roles = this.reflector.get<string[]>('roles', context.getClass());
// Get metadata from both (handler overrides class)
const roles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
// Get metadata from both (merge arrays)
const roles = this.reflector.getAllAndMerge<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
Feature Flag Guard
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ConfigService } from '@nestjs/config';
export const FEATURE_FLAG_KEY = 'featureFlag';
export const RequireFeature = (flag: string) =>
SetMetadata(FEATURE_FLAG_KEY, flag);
@Injectable()
export class FeatureFlagGuard implements CanActivate {
constructor(
private reflector: Reflector,
private configService: ConfigService,
) {}
canActivate(context: ExecutionContext): boolean {
const requiredFeature = this.reflector.get<string>(
FEATURE_FLAG_KEY,
context.getHandler(),
);
if (!requiredFeature) {
return true;
}
const isEnabled = this.configService.get(`features.${requiredFeature}`);
return isEnabled === true;
}
}
// Usage
@Controller('beta')
export class BetaController {
@Get('feature')
@RequireFeature('newFeature')
getBetaFeature() {
return 'Beta feature';
}
}
Complete Example
// auth.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { IS_PUBLIC_KEY } from './decorators/public.decorator';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private reflector: Reflector,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token);
request['user'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './guards/auth.guard';
import { RolesGuard } from './guards/roles.guard';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
// Usage in controller
@Controller('cats')
export class CatsController {
@Get()
@Roles(Role.Admin)
findAll() {
return 'Admin only';
}
@Post('login')
@Public()
login() {
return 'Public route';
}
}
Best Practices
- Single responsibility - Each guard should check one thing
- Use dependency injection - Inject services for complex validation
- Throw appropriate exceptions - Use
UnauthorizedExceptionorForbiddenException - Leverage Reflector - Use custom decorators for metadata
- Order matters - Apply guards in the correct order (auth before roles)
- Make routes public explicitly - Use
@Public()decorator for clarity - Return early - Check for public routes first to avoid unnecessary processing
- Use global guards - For application-wide authentication
- Cache metadata - Use
getAllAndOverridefor better performance - Document guard behavior - Clear comments on what each guard protects
Guards vs Middleware
Guards:
- Execute after middleware
- Have access to
ExecutionContext - Know which handler will execute
- Better for authentication/authorization
- Can use dependency injection easily
- Work across all transport layers (HTTP, WebSockets, Microservices)
Middleware:
- Execute before guards
- No access to execution context
- Don't know which handler will execute
- Better for general request processing
- Simpler for basic operations
Request Lifecycle Position
Incoming Request
↓
Middleware
↓
Guards ← (You are here)
↓
Interceptors (before)
↓
Pipes
↓
Route Handler
↓
Interceptors (after)
↓
Exception Filters
↓
Response
Guards are the gatekeepers that decide if a request should proceed to the handler based on the execution context.
Weekly Installs
1
Repository
ramziddin/ccpluginsGitHub Stars
1
First Seen
6 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
warp1