2

Current behavior

The documentation states here that:

...we passed the RolesGuard type (instead of an instance), leaving responsibility for instantiation to the framework and enabling dependency injection.

So I'm expecting to "override" a guard with another, via the module's providers. This doesn't work as expected.

Interestingly enough, injecting the same service via the controller's constructor does yield the correct service, though.

Input Code

Simple: https://github.com/dima-gusyatiner/nestjs-guards-override/tree/only-app-module

With Another module, using exports: https://github.com/dima-gusyatiner/nestjs-guards-override/tree/master

Controller:

@Controller()
@UseGuards(AuthGuard)

Module:

@Module({
  controllers: [AppController],
  providers: [
    {
      provide: AuthGuard,
      useClass: AuthOverrideGuard,
    }
  ],
})

Expected behavior

I would expect AuthGuard to never even be constructed. Instead, both classes are constructed, and AuthGuard is used as the guard.

Environment

- Nest version: 8.0.6
- Node version: 16.8.0
- Platform: Linux

Bug Report

I've also opened a ticket for this: https://github.com/nestjs/nest/issues/8011

Question

Am I doing something wrong here, or is this a bug? Anybody can suggest a workaround?

Dimdum
  • 322
  • 2
  • 15
  • It's pretty interesting case I have to say. Anyway, can you explain why you need it? It looks like you're doing something wrong if you need it (even if it's a bug) – Marek Urbanowicz Sep 03 '21 at 12:09

2 Answers2

0

I am not sure I understand it that well either but from what I saw:

  • With UseGuards, the passed in class is not resolved as provider. Only when you declare the guard as a dependency. Just like in AppController, is it looked up in the provider scope.
  • Sure the guard's dependencies are resolved as providers within the calling module's scope but it itself is not. Just regular instantiation with dependency injection.
  • I think that is why both guards are still created, just that AuthOverrideGuard, is registered as a provider.

That is why you get the following output:

[Nest] 77074  - 05/09/2021, 05:01:39     LOG [NestFactory] Starting Nest application...
Construct AuthOverrideGuard
Construct AuthGuard
AuthGuard
AppController AuthOverrideGuard {}
  • This is why this.guard is correctly resolved as AuthOverrideGuard from the provider scope
0

To summarize, in my understanding, the docs are inaccurate.

The documentation states here that:

...we passed the RolesGuard type (instead of an instance), leaving responsibility for instantiation to the framework and enabling dependency injection.

This is inaccurate, since RolesGuard won't be instantiated with proper dependency injection. Instead, it will be instantiated using this exact class only.

To achieve proper dependency injection, one should use another class, injected via the guard's constructor. For example:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

import { AuthService } from '../services';

@Injectable()
export class AuthGuard implements CanActivate {
    constructor(
        private readonly auth: AuthService,
    ) {}

    canActivate(context: ExecutionContext) {
        return this.auth.canActivate(context);
    }
}

You can never provide another AuthGuard, it won't work. But you can provide another AuthService, which this guard uses.

@Module({
  controllers: [AppController],
  providers: [
    {
      provide: AuthService,
      useClass: AuthServiceOverride,
    }
  ],
})
Dimdum
  • 322
  • 2
  • 15