NestJS Guards Guide
NestJS guards determine whether a request should be handled. Learn JWT guards, role-based access, @UseGuards decorator, and global guard registration.
1. JWT Auth Guard
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { Reflector } from '@nestjs/core';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private configService: ConfigService,
private reflector: Reflector,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// Check for @Public() decorator
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
const request = context.switchToHttp().getRequest();
const token = this.extractToken(request);
if (!token) throw new UnauthorizedException('Missing token');
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: this.configService.get('JWT_SECRET'),
});
request['user'] = payload;
return true;
} catch {
throw new UnauthorizedException('Invalid token');
}
}
private extractToken(request: Request): string | undefined {
const [type, token] = request.headers['authorization']?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
2. Role-Based Guard
import { SetMetadata } from '@nestjs/common';
// Decorator to set required roles
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// Public route decorator
export const Public = () => SetMetadata('isPublic', true);
// Role guard
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles || requiredRoles.length === 0) return true;
const { user } = context.switchToHttp().getRequest();
if (!user) return false;
// user.roles is an array
return requiredRoles.some(role => user.roles?.includes(role));
}
}
// Controller usage
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class AdminController {
@Get('users')
getUsers() { ... }
@Delete('users/:id')
@Roles('admin', 'superadmin') // override class-level
deleteUser(@Param('id') id: string) { ... }
@Get('health')
@Public() // skip auth entirely
health() { return 'ok'; }
}
3. Global Guard Registration
// app.module.ts — register globally
@Module({
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
// All routes now protected by default
// Use @Public() to opt-out specific routes
// Alternative: main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new JwtAuthGuard(jwtService, configService, reflector));
4. Passport Integration
// JWT strategy
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
});
}
async validate(payload: any) {
return { id: payload.sub, email: payload.email, roles: payload.roles };
}
}
// Use with @UseGuards(AuthGuard('jwt'))
@Get('profile')
@UseGuards(AuthGuard('jwt'))
getProfile(@Request() req) {
return req.user;
}
5. API Key Guard
@Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(private readonly apiKeysService: ApiKeysService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const apiKey = request.headers['x-api-key'] ?? request.query.api_key;
if (!apiKey) throw new UnauthorizedException('API key required');
const key = await this.apiKeysService.validate(apiKey);
if (!key) throw new UnauthorizedException('Invalid API key');
request['apiKey'] = key;
return true;
}
}
@Controller('api/v1')
@UseGuards(ApiKeyGuard)
export class ApiController { ... }
6. Guard Execution Order
| Level | How to Apply | Priority |
|---|---|---|
| Global | APP_GUARD or useGlobalGuards | Lowest scope, first executed |
| Controller | @UseGuards on class | Middle |
| Method | @UseGuards on method | Highest specificity |