Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inject service into guard in Nest.JS

I have KeysModule, which can be used to add or remove API keys. I need these keys to protect some routes from unauthorized access. To protect these routes I have created ApiGuard:

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

@Injectable()
export class ApiGuard implements CanActivate {

async canActivate(
    context: ExecutionContext,
  ): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    return request.headers.api_key;
  }
}

And then I use it in route:

 @Get('/protected')
 @UseGuards(ApiGuard)
 async protected(@Headers() headers: Api) {
   const key = await this.ks.findKey({ key: headers.api_key });
   if (!key || !key.active) return 'Invalid Key';
   return 'Your API key works';
 }

Where ks is KeyService used to check if key is correct or not. This solution works, but is stupid. I have to copy and paste some lines of code everywhere I want to use this guard (I mean lines in route).

I have tried to to move all logic to ApiGuard, but there I have got error, that KeyService cannot be injected to ApiGuard class. To explain, I have KeyService in providers in KeysModule, but ApiGuard is globally used.

Do you have any idea how to do it?

like image 343
Greggy Avatar asked Oct 17 '18 19:10

Greggy


3 Answers

As of NestJS v8 it seems injecting the service as answered by zsoca in the accepted answer doesn't work anymore.

The working solution for NestJS 8 is by providing a class reference instead of a string:

  constructor(@Inject(KeyService) private keyService: KeyService) {}
like image 186
MysticEarth Avatar answered Nov 09 '22 15:11

MysticEarth


To inject service in guard.You can create a global module.

// ApiModule
import {Module,Global} from '@nestjs/common';
import {KeyService} from '../';

@Global()
@Module({
    providers: [ KeyService ],
    exports: [KeyService]
})
export class ApiModule {}

Then inject service into guard like this

// guard
export class ApiGuard implements CanActivate {
constructor(@Inject('KeyService') private readonly KeyService) {}
}
 async canActivate(context: ExecutionContext) {
    // your code
    throw new ForbiddenException();
  }

Now the problem can be solved.But I have another problem.I want to inject something into service but got this error:

Nest can't resolve dependencies of the AuthGuard (?, +). Please make sure that the argument at index [0] is available in the current context.

And here is my solution:

To Inject other dependency in KeyService,like nestjs docs say.

global guards registered from outside of any module (with useGlobalGuards() as in the example above) cannot inject dependencies since this is done outside the context of any module.

This is their sample:

// app.module.js
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class ApplicationModule {}

It worked.Now I can use guard global without dependency error.

like image 9
Jinni Avatar answered Nov 09 '22 16:11

Jinni


Maybe it's too late, but I ran the same issue and find a solution. Maybe there is a better one, but it's working properly for me:

Define KeysModule as a global module, you can check how to do it in nestjs docs: https://docs.nestjs.com/modules

After You can do this:

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

@Injectable()
export class ApiGuard implements CanActivate {

constructor(
@Inject('KeyService')
private readonly ks
) {}

const key = await this.ks.findKey();

"YOUR_CODE_HERE..."

}

Hope it's help to you or somebody who will stuck with this in the future.

like image 7
Zsoca Avatar answered Nov 09 '22 16:11

Zsoca