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:
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.
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();
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
.
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.