Rob Bailey
Rob Bailey

Reputation: 981

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 {}

Upvotes: 17

Views: 18880

Answers (3)

Jayanth MKV
Jayanth MKV

Reputation: 323

You can also try to add this

@Module({
  imports: [
    JwtModule.register({
      secret: process.env.JWT_SECRET,
      signOptions: { expiresIn: '5m' },
    }),
    ConfigModule.forRoot({
      isGlobal: true,
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, PrismaService, LocalStrategy, JwtStrategy, ConfigService, RefreshJwtStrategy]
})
export class AuthModule { }

Upvotes: 0

hungify
hungify

Reputation: 397

If @Jay McDoniel's answer doesn't work, please ensure the env file existence and correct the env key ("JWT_KEY")

Upvotes: 0

Jay McDoniel
Jay McDoniel

Reputation: 70510

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.

Upvotes: 36

Related Questions