136 lines
3.6 KiB
Markdown
136 lines
3.6 KiB
Markdown
---
|
|
title: Use Guards for Authentication and Authorization
|
|
impact: HIGH
|
|
impactDescription: Enforces access control before handlers execute
|
|
tags: security, guards, authentication, authorization
|
|
---
|
|
|
|
## Use Guards for Authentication and Authorization
|
|
|
|
Guards determine whether a request should be handled based on authentication state, roles, permissions, or other conditions. They run after middleware but before pipes and interceptors, making them ideal for access control. Use guards instead of manual checks in controllers.
|
|
|
|
**Incorrect (manual auth checks in every handler):**
|
|
|
|
```typescript
|
|
// Manual auth checks in every handler
|
|
@Controller('admin')
|
|
export class AdminController {
|
|
@Get('users')
|
|
async getUsers(@Request() req) {
|
|
if (!req.user) {
|
|
throw new UnauthorizedException();
|
|
}
|
|
if (!req.user.roles.includes('admin')) {
|
|
throw new ForbiddenException();
|
|
}
|
|
return this.adminService.getUsers();
|
|
}
|
|
|
|
@Delete('users/:id')
|
|
async deleteUser(@Request() req, @Param('id') id: string) {
|
|
if (!req.user) {
|
|
throw new UnauthorizedException();
|
|
}
|
|
if (!req.user.roles.includes('admin')) {
|
|
throw new ForbiddenException();
|
|
}
|
|
return this.adminService.deleteUser(id);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Correct (guards with declarative decorators):**
|
|
|
|
```typescript
|
|
// JWT Auth Guard
|
|
@Injectable()
|
|
export class JwtAuthGuard implements CanActivate {
|
|
constructor(
|
|
private jwtService: JwtService,
|
|
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('No token provided');
|
|
}
|
|
|
|
try {
|
|
request.user = await this.jwtService.verifyAsync(token);
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Roles Guard
|
|
@Injectable()
|
|
export class RolesGuard implements CanActivate {
|
|
constructor(private reflector: Reflector) {}
|
|
|
|
canActivate(context: ExecutionContext): boolean {
|
|
const requiredRoles = this.reflector.getAllAndOverride<Role[]>('roles', [
|
|
context.getHandler(),
|
|
context.getClass(),
|
|
]);
|
|
|
|
if (!requiredRoles) return true;
|
|
|
|
const { user } = context.switchToHttp().getRequest();
|
|
return requiredRoles.some((role) => user.roles?.includes(role));
|
|
}
|
|
}
|
|
|
|
// Decorators
|
|
export const Public = () => SetMetadata('isPublic', true);
|
|
export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
|
|
|
|
// Register guards globally
|
|
@Module({
|
|
providers: [
|
|
{ provide: APP_GUARD, useClass: JwtAuthGuard },
|
|
{ provide: APP_GUARD, useClass: RolesGuard },
|
|
],
|
|
})
|
|
export class AppModule {}
|
|
|
|
// Clean controller
|
|
@Controller('admin')
|
|
@Roles(Role.Admin) // Applied to all routes
|
|
export class AdminController {
|
|
@Get('users')
|
|
getUsers(): Promise<User[]> {
|
|
return this.adminService.getUsers();
|
|
}
|
|
|
|
@Delete('users/:id')
|
|
deleteUser(@Param('id') id: string): Promise<void> {
|
|
return this.adminService.deleteUser(id);
|
|
}
|
|
|
|
@Public() // Override: no auth required
|
|
@Get('health')
|
|
health() {
|
|
return { status: 'ok' };
|
|
}
|
|
}
|
|
```
|
|
|
|
Reference: [NestJS Guards](https://docs.nestjs.com/guards)
|