node.jsauthenticationjwtpassport.jsnestjs

NestJS JWT Strategy requires a secret or key


In my NestJS Node application, I have set up JWT authentication using Passport (as per https://docs.nestjs.com/techniques/authentication) however I am attempting to keep the JWT Key in environment files which are retrieved using the built-in ConfigService.

export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get<string>('JWT_KEY'),
      signOptions: { expiresIn: '60s' }
    });
  }

The module is registered as follows:

JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => {
        return {
          secret: configService.get<string>('JWT_KEY')
        };
      },
      inject: [ConfigService]
    })

I am getting the following error when starting the app:

api: [Nest] 16244   - 03/27/2020, 10:52:00   [ExceptionHandler] JwtStrategy requires a secret or key +1ms

It appears that the JWTStrategy class is instantiating before the ConfigService is ready to provide the JWT Key and is returning undefined within the Strategy when calling configService.get<string>('JWT_KEY').

What am I doing wrong here? How can I ensure that the ConfigService is ready prior to attempting to retrieve any environment variables?

UPDATE: Entire AuthModule is below:

import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategies/jwt.strategy';
import { LocalStrategy } from './strategies/local.strategy';
import { SharedModule } from '../shared/shared.module';
import { UsersModule } from '../users/users.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';

const passportModule = PassportModule.register({ defaultStrategy: 'jwt' });
@Module({
  imports: [
    UsersModule,
    passportModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => {
        return {
          secret: configService.get<string>('JWT_KEY')
        };
      },
      inject: [ConfigService]
    })
  ],
  providers: [ConfigService, AuthService, LocalStrategy, JwtStrategy],
  controllers: [AuthController],
  exports: [passportModule]
})
export class AuthModule {}

Solution

  • I'm going to be willing to bet that the issue is that you are not importing the ConfigModule to the AuthModule and instead you are adding the ConfigService to the providers array directly. This would mean that if ConfigModule does any sort of set up on the ConfigService, it won't be happening anymore. What you should have instead is something like this:

    @Module({
      imports: [
        PassportModule.register({defaultStrategy: 'jwt' }),
        UserModule,
        JwtModule.registerAsync({
          imports: [ConfigModule],
          useFactory: async (configService: ConfigService) => {
            return {
              secret: configService.get<string>('JWT_KEY')
            };
          },
          inject: [ConfigService]
        }),
        ConfigModule,
      ],
      providers: [LocalStrategy, JwtStrategy, AuthService],
      controllers: [AuthController],
      exports: [PassportStrategy],
    })
    export class AuthModule {}
    

    Now as long a ConfigModule exports ConfigService, the AuthModule should fire up just fine.