Having a little trouble with the registerAsync
functionality and JwtModule
+JwtService
. I looked through a lot of the other threads on this and seems like everyone was hung up on ConfigModule
, but I am not using ConfigModule
in this project.
Let me start with the set-up:
// secret-key.helper.ts
/*
Retrieve the JWT secret value from AWS Secrets Manager. This is used to
sign JWTs. Dont worry about imports, they're there.
*/
async function retrieveJwtSecret(): Promise<string> {
const secretClient = new SecretsManagerClient({
region: process.env.AWS_REGION,
});
const getSecretCom = new GetSecretValueCommand({
SecretId: process.env.JWT_SECRET_ARN,
});
const jwtSecret = (await secretClient.send(getSecretCom)).SecretString;
return jwtSecret;
}
// auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtModule } from '@nestjs/jwt';
import { retrieveJwtSecret } from '../../utils/secret-key.helper';
@Module({
imports: [
JwtModule.registerAsync({
useFactory: async () => ({
global: true,
secret: await retrieveJwtSecret(),
signOptions: { expiresIn: '16h' },
}),
}),
],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
// my.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Request } from 'express';
import { logEvent } from '../log-event.helper';
import { JwtService } from '@nestjs/jwt';
import { JwtPayloadDto } from 'src/models/auth/dto/jwt-payload.dto';
@Injectable()
export class MyGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
/* JWT Authentication */
const token = this.extractTokenFromHeader(req);
if (!token) {
logEvent('ERROR: No token found.', req.headers);
return false;
} else {
const payload = this.jwtService.decode(token) as JwtPayloadDto;
logEvent('Token found.', payload);
return true;
}
}
}
So the first method is how I retrieve my JWT secret key from AWS secret manager. The second code block is the auth
module showing the registerAsync
global import and the usage of the retrieveJwtSecret()
method (this is what is causing problems). The third code block is the guard I am using.
The first module where Nest runs into the Guard being imported/used returns a "can't resolve dependencies" error. When I switch away from registerAsync
to register
, pull the secret key in main.ts
and place it in process.env
manually, everything runs fine.
The exact error is:
ERROR [ExceptionHandler] Nest can't resolve dependencies of the MyGuard (?).
Please make sure that the argument JwtService at index [0] is available in the BenefitsModule context.
Potential solutions: ...
Through the use of console.log
, I've found that execution begins in the retrieveJwtSecret
function and that at run time, it has access to the .env variables necessary. However, it never fully completes the const jwtSecret = await secretClient.send...
call, and my guess is that Nest continues to load modules that require JwtModule
before Nest receives the secret and finishes setting up JwtModule
.
I'd like to stay away from the register
implementation I mentioned, if possible. Is there a way to force Nest to wait for async
dependencies to be await
-ed on?
Thanks!
The global
option of JwtModule.registerAsync
should be on the same level as useFactory
, not a part of the options returned by the useFactory
. So your module should be:
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtModule } from '@nestjs/jwt';
import { retrieveJwtSecret } from '../../utils/secret-key.helper';
@Module({
imports: [
JwtModule.registerAsync({
global: true,
useFactory: async () => ({
secret: await retrieveJwtSecret(),
signOptions: { expiresIn: '16h' },
}),
}),
],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
This will make the JwtModule
actually global.