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 {}
I'm going to be willing to bet that the issue is that you are not import
ing 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.