NestJS custom PassportStrategy not registered when shared through library

11.7k Views Asked by At

I've been tasked with isolating our NestJS authentication module into a separate shared library, in order for it to be reused between multiple NestJS projects. Each project lives in each own repository and the shared library is imported as an npm package. I'm using NestJS' passport module for the authentication (using jwt tokens) and have basically just followed the official documentation on how to implement it.

I've followed the pattern of other NestJS community packages, and so far I've moved most of the authentication code over and made sure it could compile and run.

Now, I've come across an issue. The application no longer recognizes the custom jwt passport strategy, after I moved it over to the library and I have no idea why. I just get the exception:

Unknown authentication strategy "jwt"

Example:

This is the custom passport strategy and the AuthModule (the real version is more complex, but this is a minimal reproduceable example). The code is exactly the same in both the "parent"-project and the new library-project.

import { Injectable, Module } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-custom';

@Injectable()
export class CustomStrategy extends PassportStrategy(Strategy, 'custom') {
  async validate(request: Request): Promise<any> {
    console.log('Custom validation', request);

    return true;
  }
}

@Module({})
export class AuthModule {
  static forRoot() {
    return {
      module: AuthModule,
      providers: [CustomStrategy],
      exports: [CustomStrategy],
    };
  }
}

This is how I register it in the NestJS application:

import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { AuthModule } from '@my-org/common';        // Library version
// import { AuthModule } from './auth/AuthModule';  // Local version

@Module({
  imports: [
    AuthModule.forRoot(),
    PassportModule.register({ defaultStrategy: 'custom' })
  ]
})
export class CommonModule {}

export class CustomAuthGuard extends AuthGuard('custom') {}

When the AuthModule is refererenced from within the same NestJS project, everything runs and I get the Custom validation console message.

When the AuthModule is imported from my npm library, I get the Unknown authentication strategy "custom" exception from passport, whenever I make a request.

The code on both sides is exactly the same. The usage of the module is exactly the same, regardless of where the module comes from. The shared library is set up using the same pattern as official NestJS packages, such as @nestjs/common and @nestjs/cqrs. Any other registered services exported by the library works as intended (I can include another minimal example to show this if requested).

What am I missing? How can I share my custom NestJS PassportStrategy using a separate npm package?

I've spent half my workday trying to solve this issue, and so far I think it might have something to do with which passport instance the strategy is registered with, but I don't know how to test this - or, if that is even the case, how to solve it.

4

There are 4 best solutions below

0
On

So, after a lot of research, one of my colleagues found what is the issue. Guards are not providers and they should not be registered as providers. And here is the full explanation: https://github.com/nestjs/nest/issues/3856

0
On

Something like this works for me:

import { Injectable, Module } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-custom';

@Injectable()
export class CustomStrategy extends PassportStrategy(Strategy, 'custom') {
  async validate(request: Request): Promise<any> {
    console.log('Custom validation', request);

    return true;
  }
}

@Module({})
export class AuthModule {
  static forRoot() {
    return {
      module: AuthModule,
      imports: [
        PassportModule.register({ defaultStrategy: 'custom' }),
      ],
      providers: [CustomStrategy],
      exports: [CustomStrategy, PassportModule],
    };
  }
}
0
On

Strategy:

import { Strategy } from 'passport-custom';
import { PassportStrategy } from '@nestjs/passport';
import {Injectable, UnauthorizedException} from '@nestjs/common';

@Injectable()
export class CustomStrategy extends PassportStrategy(Strategy, "custom") {
    static key = "custom"

    async validate(req: Request): Promise<User> {
        const valid = true;
        if(!valid) throw new UnauthorizedException();
        return {id: "123", name: "test"};
    }
}

Auth Module:

@Module({
  imports: [PassportModule],
  providers: [CustomStrategy]
})
export class AuthModule {}

Controller Decorator:

@UseGuards(AuthGuard(CustomStrategy.key))
0
On

For passport custom strategy:

import { Strategy } from 'passport-custom';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { AuthService } from '../auth.service';
import { UnauthorizedException } from 'src/shared/exception';

@Injectable()
export class CustomStrategy extends PassportStrategy(Strategy, 'custom') {
  constructor(private authService: AuthService) {
    super();
  }

  async validate(req: Request): Promise<any> {
    const token = req.headers['x-access-token'];
    const tokenInfo = await this.authService.validateToken(token);
    if (!tokenInfo) {
      throw new UnauthorizedException(`Token is not valid`);
    }
    const user = await this.authService.getUserInfo(tokenInfo.userId);
    if (!user) {
      throw new UnauthorizedException(`User is not valid`);
    }
    return user;
  }
}

For auth module, import PassportModule and using providers CustomStrategy:

import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { UserModule } from '../user/user.module';
import { AuthService } from './auth.service';
import { CustomStrategy } from './strategies/custom.strategy';

@Module({
  imports: [UserModule, PassportModule],
  providers: [AuthService, CustomStrategy],
})
export class AuthModule {}

Ref: