NestJS guards: why unguarded routes fail open
rbac-fs/nestjs gives you RbacGuard, @RequirePermission(), and provideRbac() — a decorator-driven guard, not a global middleware that inspects every route.
The shape
import { Controller, Post, UseGuards, Module } from '@nestjs/common';
import { RbacGuard, RequirePermission, provideRbac } from 'rbac-fs/nestjs';
@Controller('invoices')
export class InvoiceController {
@Post(':id/approve')
@UseGuards(RbacGuard)
@RequirePermission('invoice', 'approve')
approve() {
return { approved: true };
}
@Post(':id/comment') // no @RequirePermission() -> RbacGuard lets it through unchecked
comment() {
return { commented: true };
}
}
@Module({
controllers: [InvoiceController],
providers: [provideRbac(rbac)], // binds your RBAC instance to RBAC_TOKEN
})
export class AppModule {}Why fail-open on missing metadata, not fail-closed
A route decorated with @UseGuards(RbacGuard) but no @RequirePermission() is let through unchecked, rather than rejected. This is opt-in by design: RbacGuard only enforces what you've explicitly declared. It's not a substitute for authentication — pair it with your own auth guard (AuthGuard('jwt') or equivalent) so request.user is already populated by the time RbacGuard runs.
What happens without a user on the request
// route has @RequirePermission() but request.user is missing
await guard.canActivate(context); // throws ForbiddenExceptionMissing request.user on a *guarded* route throws — that combination (decorator present, user absent) is the one case RbacGuard treats as a hard failure, since there's nothing to evaluate against.
For a step-by-step demo you can run with tsx, no @nestjs/cli build required, see Tutorial: wiring up the NestJS adapter.