Maurice Wipf
Maurice Wipf

Reputation: 707

How to provide a Guard for a specific Module in Nest.js?

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

Answers (4)

Bahador Raghibizadeh
Bahador Raghibizadeh

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

glothos
glothos

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

Adrien De Peretti
Adrien De Peretti

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

Qiaoge
Qiaoge

Reputation: 51

You can add @UseGuards(JwtAuthGuard) before the controller class,and make the guard protect all routes on the controller.

Upvotes: 5

Related Questions