Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 {}
like image 841
Rob Bailey Avatar asked Mar 27 '20 11:03

Rob Bailey


People also ask

What is strategy in NestJS?

It is a way to define custom algorithm/logic to authenticate users. Passport has a lot of strategies like JWT, facebook, google and more.. You extend a strategy and add your custom logic like from where to get the user, how to validate the user and options passed to passport.

What is JWT in NestJS?

JWT stands for JSON Web Tokens. Using JWT effectively can make our applications stateless from an authentication point of view. We will be using the NestJS JWT Authentication using Local Strategy as the base for this application.

What is a JWT strategy?

A Passport strategy for authenticating with a JSON Web Token. This module lets you authenticate endpoints using a JSON web token. It is intended to be used to secure RESTful endpoints without sessions.

Is NestJS secure?

NestJS follows mostly the same security rules as the Node. js server and Express. NestJS has an dedicated security section in its documentation that addresses these topics: Authentication.


1 Answers

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.

like image 114
Jay McDoniel Avatar answered Sep 27 '22 23:09

Jay McDoniel