Reputation: 707
I have a module called AdminModule
which should be protected by AdminGuard
.
I tried to set up the Guard directly in the module like this:
@Module({
imports: [
HttpModule,
],
controllers: [AdminController],
providers: [
{
provide: APP_GUARD,
useClass: AdminGuard,
},
AdminService,
],
})
export class AdminModule {
}
However, the guard is not limited to this module but it is global (as stated in the docs: "the guard is, in fact, global"
).
But how is it possible to make the guard only protect a module?
Upvotes: 12
Views: 18305
Reputation: 1265
I had exactly the same problem as you. I used a Middleware
as a Guard
.
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { ApiModule } from './api/api.module';
import { AdminGuardMiddleware } from './api/v1/admin/admin.guard.middleware';
@Module({
imports: [
ApiModule,
],
controllers: [],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AdminGuardMiddleware)
.forRoutes({ path: '/api/v1/admin/*', method: RequestMethod.ALL });
}
}
admin.guard.middleware.ts:
import { HttpException, HttpStatus, Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class AdminGuardMiddleware implements NestMiddleware {
constructor() { }
use(req: Request, res: Response, next: NextFunction) {
try {
// you can check anything here
next();
} catch (error) {
throw new HttpException("invalid_request", HttpStatus.FORBIDDEN);
}
}
}
Upvotes: 1
Reputation: 38
This question is a bit old, but there's a hacky way to do it in a controller level by declaring the controllers of a specific module. Guard context can access the type of the controller class the request is targeting. You can access it inside the guard itself.
const PROTECTED_CONTROLLERS = [CatsController, SomeOtherController, UserController];
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
// this gets the controller class
const controllerCls = context.getClass();
if (!PROTECTED_CONTROLLERS.includes(controllerCls)) {
// this guard is not important for context, so we should always allow request to continue
return true;
}
// check if controller method contains @Public() decorator
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC, [
context.getHandler(),
controllerCls,
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}
And then, you can add this guard as a global Guard like so:
@Module({
providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }]
})
export class MyModule {}
You should note that if you need other guards, the position in providers array is important
@Module({
providers: [
{
provide: APP_GUARD,
useClass: SomeOtherGuard, // <-- it will be executed first
},
{
provide: APP_GUARD,
useClass: JwtAuthGuard, // <-- it will be executed second if first guard passes
},
})
export class MyModule {}
This is not a great way to do it and I recommend use decorators like the answers above. However, it can be useful in certain use cases where there's the need to have main modules and submodules in more complex applications.
If you are worried about verbosity of decorators on top of classes, you can use decorator composition for that.
Upvotes: 0
Reputation: 3662
Update : there is actually no options to achieve that.
Information :
What you've done by using APP_GUARD
is to apply it globally. It's the same as using useGlobalGuards
, but this way allows you to take advantage of the DI system.
{
provide: APP_GUARD,
useClass: AdminGuard,
},
If you don't want to apply it globally, don't add this to the provider's array in the module.
Instead, just create a new guard like this
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
See the documentation here: https://docs.nestjs.com/guards
And then apply it on your controller at the class level to impact all the handlers of the controller, or to a method to impact a specific endpoint.
@UseGuards(RolesGuard)
Upvotes: 12
Reputation: 51
You can add @UseGuards(JwtAuthGuard)
before the controller class,and make the guard protect all routes on the controller.
Upvotes: 5