Although I can handle authorization at the controller level using @UseGuards()
, for this specific use case I prefer to implement authorization checks at the service level. However, since NestJS's built-in @UseGuards()
only works at the controller level but not for service methods, I'm implementing a custom decorator for role-based authorization. Here's a minimal example:
// Proposed decorator approach since @UseGuards doesn't work for service methods
@Injectable()
export class ProjectService {
constructor(private readonly authService: ProjectAuthService) {}
@ProjectRoleGuard('admin', 'owner')
async addProject(data: { projectId: string }) {
// ... method implementation
}
}
// Decorator implementation
export function ProjectRoleGuard(...roles: string[]) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const authService = Object.values(this).find(
(value) => value instanceof ProjectAuthService
);
const { projectId } = args[0];
if (!await authService.isCurrentUserInProjectRole({
projectId,
roles
})) {
throw new UnauthorizedException();
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
The decorator approach looks cleaner and more reusable than inline checks, but I want to ensure it's as secure as the inline approach since we're working around NestJS's guard limitations. Are there any security concerns or best practices I should consider when using this pattern?
Would love to hear thoughts on:
- Security implications
- Potential edge cases
- Better ways to handle dependency injection
- Performance considerations
- Alternative approaches for service-level authorization in NestJS