nestjs / jwt

JWT utilities module based on the jsonwebtoken package 🔓
https://nestjs.com
MIT License
602 stars 84 forks source link

signAsync and verifyAsync promises never resolve #1739

Closed ethan-far closed 1 month ago

ethan-far commented 1 month ago

Is there an existing issue for this?

Current behavior

Hi, and thanks for a fanstastic package!

I'm running into an issue with unresolved promises, now that I've switched to async secret providers as part of production-ready preparations. It seems that the code in signAsync and verifyAsync never resolves, since it resolves the original promise returned from private getSecret(...) immediately upon creation rather when it's being awaited and therefore, the returned promises are stuck in a never ending loop. I'm using the out-of-the-box AuthGuard, with the following strategy implementation:

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
    constructor(
        readonly secretsService: SecretsService,
        private readonly authenticationService: AuthenticationService
    ) {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            secretOrKeyProvider: async () => {
                return new Promise((resolve, reject) => {
                    secretsService.getSecret(Secret.JwtEncryptionKey)
                        .then(secret => {
                            console.log('resolving secret: ', secret)
                            resolve(secret)
                        })
                        .catch(error => {
                            console.error(error);
                            reject(error)
                        });
                });
            }
        });
    }

    async validate(payload: JwtPayload) {
        console.log('validating payload: ', payload);

        const user = await this.authenticationService.validateUser(payload.email);

        if (user) {
            return user;
        } else {
            throw new UnauthorizedException();
        }
    }
}

I can see that the secret is resolved properly, no error is caught, and that the validation never takes place. Looking at code in question, I've noticed the following returned promises which seemed like they would never resolve, since the secret was already resolved upon creation:

return new Promise((resolve, reject) =>
      Promise.resolve()
        .then(() => secret)
        .then((scrt: GetSecretKeyResult) => {
          jwt.sign(payload, scrt, signOptions, (err, encoded) =>
            err ? reject(err) : resolve(encoded)
          )
        })
    );

To test my theory, I did something similar in a Node.js shell, and it also didn't resolve: image

The interrupt is due to me pressing ctrl+c to stop the endless wait.

Please let me know if I'm missing something, or if you need further information. Thanks in advance for the help!

Minimum reproduction code

https://gist.github.com/ethan-far/d34a5fde0d6fed2c124adeb44b6c29d1

Steps to reproduce

No response

Expected behavior

The expected behavior is to be able to await the promises returned from signAsync and verifyAsync, which should terminate once the secret awaited for is available.

Package version

10.2.0

NestJS version

10.3.7

Node.js version

v20.12.0

In which operating systems have you tested?

Other

No response

kamilmysliwiec commented 1 month ago

Would you like to create a PR for this issue?

ethan-far commented 1 month ago

After trying to recreate the problem on a fork, I figured out that I'd forgotten to resolve the promise in the Node.js shell, which is why the promises never resolved, which is why the poc failed. Therefore, the problem must be somewhere in the integration with AuthGuard which is in a different repo, and I'm not sure an integration test with another repo is relevant here. In any case, the reported bug is not really a bug so I'm closing this issue.