简体   繁体   中英

JwtModule.registerAsync not working in NestJS

I'm working on a NestJS project and I need to use JWT with .env configuration. It generates the token but when trying to access the secured url(With Authorization header), it just returns the Unauthorized message.

jwt.strategy.ts

 import { Injectable, UnauthorizedException, Logger } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { AuthService } from './auth.service'; import { JwtPayload } from './interfaces/jwt-payload.interface'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor(private readonly authService: AuthService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET_KEY, }); } async validate(payload: JwtPayload) { const user = await this.authService.validateUser(payload); if (;user) { throw new UnauthorizedException(); } return user; } }

auth.module.ts

 import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './jwt.strategy'; @Module({ imports: [ PassportModule.register({ defaultStrategy: 'jwt' }), JwtModule.registerAsync({ useFactory: async () => ({ secretOrPrivateKey: process.env.JWT_SECRET_KEY, signOptions: { expiresIn: process.env.JWT_EXPIRATION_TIME, }, }), }), ], providers: [AuthService, JwtStrategy], controllers: [AuthController], }) export class AuthModule {}

main.ts

 import { NestFactory } from '@nestjs/core'; import * as dotenv from 'dotenv'; import { ApiModule } from './api/api.module'; import { Logger } from '@nestjs/common'; async function bootstrap() { dotenv.config({ path: './.env'}); const app = await NestFactory.create(ApiModule); const port = process.env.APP_PORT; await app.listen(port); Logger.log(`Server started on http://localhost:${port}`); } bootstrap();

Looks like JwtModule.registerAsync is not working with environment variables. I've tried many things but it always fails. If I change environment variables in auth.module.ts for static data, then it works fine. Something like this:

secretOrPrivateKey: 'secretKey',
signOptions: {
  expiresIn: 3600,
},

UPDATE Project structure

- src
    - api
        - auth
            - interfaces
                jwt-payload.interface.ts
            auth.controller.ts
            auth.module.ts
            auth.service.ts
            jwt.strategy.ts
            index.ts
        api.module.ts
        index.ts
    main.ts
- test
.env

My main.ts looks like this now.

 import { NestFactory } from '@nestjs/core'; import * as dotenv from 'dotenv'; import { resolve } from 'path'; import { ApiModule } from './api/api.module'; import { Logger } from '@nestjs/common'; async function bootstrap() { dotenv.config({ path: resolve(__dirname, '../.env') }); const app = await NestFactory.create(ApiModule); const port = process.env.APP_PORT; await app.listen(port); Logger.log(`Server started on http://localhost:${port}`); } bootstrap();

You see that my .env is in the root of the project.

If you use config module you can do something like this:

JwtModule.registerAsync({
  useFactory: (config: ConfigService) => {
    return {
      secret: config.get<string>('JWT_SECRET_KEY'),
      signOptions: {
        expiresIn: config.get<string | number>('JWT_EXPIRATION_TIME'),
      },
    };
  },
  inject: [ConfigService],
}),

I also had a problem with initializing JwtModule and this code solved it.

For me your code works:

编辑 Nest.js JWT 身份验证

Where is your .env file? Your configuration dotenv.config({ path: './.env'}); is equal to the default configuration dotenv.config(); where the .env file is looked up in your project root directory (and not in src ).

If you want to put your .env file in the src directory, use the following configuration

import { resolve } from 'path';
dotenv.config({ path: resolve(__dirname, '.env') });

Instead of directly using your environment variables, I'd recommend encapsulating them in a ConfigService , see the docs . This makes it much easier to test and refactor.

TL;DR

Assuming you have a .env file and it's in the correct place, for this to work you need to configure dotenv before everything, even imports

// configure dotenv before every thing, even imports
import * as dotenv from 'dotenv';
import { resolve } from 'path';
dotenv.config({ path: resolve(__dirname, '../.env') });

// rest of the code
import { NestFactory } from '@nestjs/core';
import { ApiModule } from './api/api.module';
import { Logger } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(ApiModule);
  const port = process.env.APP_PORT;

  await app.listen(port);
  Logger.log(`Server started on http://localhost:${port}`);
}
bootstrap();

Why?

Because when you do something like this

import { ApiModule } from './api/api.module'

What happens is that you are running the code from the file ./api/api.module and that file will look like this (I'm using the other file you showed in you question just so it's more clear to you)

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.registerAsync({
      useFactory: async () => ({
        secretOrPrivateKey: process.env.JWT_SECRET_KEY,
        signOptions: {
          expiresIn: process.env.JWT_EXPIRATION_TIME,
        },
      }),
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
})
export class AuthModule {}

And when you import it, this whole file is "executed" and the references of process.env are "set" before you set dotenv .

So you need to "run" the code that sets the dotenv before you "execute" the code where you use process.env .

Observations:

I still recommend using the Configuration that is already built-in and then you should use the async methods and inject the configuration service (just like some other answer).

But if you do want to use process.env , setting dotenv before everything is the way to go.

Use parseInt() for process.env.JWT_EXPIRATION_TIME . The expiresIn parameter should be a number. In your case it's string.

I'm recently worked on that and just like you I tried many things but no one worked for me.. instead, that one did!

 @Module({ 
    imports: [
    AnyModule,
    JwtModule.registerAsync({
      useFactory: async (config: ConfigService) => {
        return {
          secret: process.env.JWT_SECRET,
          signOptions: { expiresIn: "120s" }
        }
      }
    }),
  ]

I use JwtModule.registerAsync() method with async useFactory and without injection of ConfigService .

Then into my AppModule.ts

@Module({
    imports: [
      ConfigModule.forRoot({
        isGlobal: true,
      })

With this, We tell to ConfigModule that it works globally and We can use it on any module without import

In short We need to import JwtModule and use registerAsync method calling userFactory object like async too. With all this done, JwtModule awaits to AppModule.ts was create for call our process.env , ConfigModule in fact.

Thanks everybody for the answer and NestJs Documentation too.

I hope this works for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM