260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
---
|
||||
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)
|
||||
Reference in New Issue
Block a user