1. Guards là gì?
Guards quyết định request có được phép tiếp tục hay không. Chúng chạy sau middleware, trước interceptors và pipes. Use case chính: Authorization — kiểm tra user có quyền truy cập resource không.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return !!request.user; // true = cho phép, false = 403
}
}
2. Role-Based Access Control (RBAC)
Custom @Roles() Decorator
// auth/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export enum Role {
Admin = 'admin',
Editor = 'editor',
User = 'user',
}
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
RolesGuard Implementation
// auth/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY, Role } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// Lấy roles yêu cầu từ metadata
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(), // Method-level
context.getClass(), // Class-level
]);
// Nếu không có @Roles() → cho phép mọi user
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.includes(user.role);
}
}
Sử dụng
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard) // Apply cho toàn controller
export class UsersController {
@Get()
@Roles(Role.Admin, Role.Editor) // Chỉ admin và editor
findAll() { ... }
@Get(':id')
@Roles(Role.Admin) // Chỉ admin
findOne(@Param('id') id: string) { ... }
@Post()
@Roles(Role.Admin)
create(@Body() dto: CreateUserDto) { ... }
@Delete(':id')
@Roles(Role.Admin)
remove(@Param('id') id: string) { ... }
}
3. Permission-based Authorization
// Permissions decorator
export enum Permission {
CreateUser = 'user:create',
ReadUser = 'user:read',
UpdateUser = 'user:update',
DeleteUser = 'user:delete',
ManageOrders = 'order:manage',
}
export const PERMISSIONS_KEY = 'permissions';
export const RequirePermissions = (...permissions: Permission[]) =>
SetMetadata(PERMISSIONS_KEY, permissions);
// Permissions Guard
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const required = this.reflector.getAllAndOverride<Permission[]>(
PERMISSIONS_KEY,
[context.getHandler(), context.getClass()],
);
if (!required) return true;
const { user } = context.switchToHttp().getRequest();
return required.every(perm => user.permissions?.includes(perm));
}
}
// Sử dụng
@RequirePermissions(Permission.DeleteUser)
@Delete(':id')
remove(@Param('id') id: string) { ... }
4. Resource Owner Guard
// Chỉ cho phép chủ sở hữu hoặc admin
@Injectable()
export class OwnerGuard implements CanActivate {
constructor(private usersService: UsersService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user;
const resourceId = request.params.id;
// Admin luôn được quyền
if (user.role === 'admin') return true;
// Kiểm tra ownership
return user.id === resourceId;
}
}
@UseGuards(JwtAuthGuard, OwnerGuard)
@Patch(':id')
updateProfile(@Param('id') id: string, @Body() dto: UpdateProfileDto) {
// Chỉ user sở hữu profile hoặc admin mới cập nhật được
}
5. CASL — Advanced Authorization
npm install @casl/ability
// casl/casl-ability.factory.ts
import { AbilityBuilder, createMongoAbility, MongoAbility } from '@casl/ability';
export type AppAbility = MongoAbility;
@Injectable()
export class CaslAbilityFactory {
createForUser(user: User): AppAbility {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
if (user.role === 'admin') {
can('manage', 'all'); // Admin có mọi quyền
} else {
can('read', 'Post');
can('create', 'Post');
can('update', 'Post', { authorId: user.id }); // Chỉ update post của mình
can('delete', 'Post', { authorId: user.id });
can('read', 'User', { id: user.id }); // Chỉ xem profile mình
can('update', 'User', { id: user.id });
cannot('delete', 'User'); // Không được xóa user
}
return build();
}
}
// Abilities Guard
@Injectable()
export class AbilitiesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory,
) {}
canActivate(context: ExecutionContext): boolean {
const rules = this.reflector.get('check-abilities', context.getHandler());
if (!rules) return true;
const { user } = context.switchToHttp().getRequest();
const ability = this.caslAbilityFactory.createForUser(user);
return rules.every(rule => ability.can(rule.action, rule.subject));
}
}
6. Composite Auth Decorator
// Kết hợp nhiều guards thành 1 decorator gọn
import { applyDecorators, UseGuards } from '@nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
Roles(...roles),
UseGuards(JwtAuthGuard, RolesGuard),
);
}
// Sử dụng cực gọn
@Auth(Role.Admin)
@Delete(':id')
remove(@Param('id') id: string) { ... }
@Auth(Role.Admin, Role.Editor)
@Post()
create(@Body() dto: CreatePostDto) { ... }
7. Tổng kết
- Guards: Quyết định request có được tiếp tục không (canActivate)
- RBAC: Role-based qua @Roles() + RolesGuard
- Permissions: Fine-grained với permission strings
- Owner Guard: Resource ownership checking
- CASL: Attribute-based access control cho logic phức tạp
- Composite Decorators: @Auth() gộp nhiều guards
Bài tiếp theo sẽ tìm hiểu Security Best Practices trong NestJS.