使用 NestJS、JWT 和 Socket.IO 进行依赖注入和回调

发布于 2025-01-10 21:32:00 字数 3075 浏览 0 评论 0原文

我正在努力理解依赖注入如何与回调一起工作。我正在使用 NestJS 使用 jsonwebtoken 包构建 Socket.IO 防护,当我尝试访问验证函数回调中的注入函数时,收到以下错误:

TypeError: Cannot readproperties of undefined (reading 'configService ')

注入的函数在回调之外工作,我知道回调是异步的,并且使用此函数我无法使用 async/await。我还必须对此函数使用回调,因为我使用 JWKS 客户端异步拉取密钥。

我想做的是注入我的 Typeorm 存储库,访问我的用户实体,并将其注入到 Socket.IO 标头中。我能够成功注入手动创建的对象,但我需要访问存储库。

我在 NestJS 论坛上发现了一个建议,涉及使用 bindNodeCallback 创建一个可观察对象,但我无法让它工作,并希望有人可以提出一个解决方案,或者帮助编写代码以使用 bindNodeCallback 来为我获取解码的令牌来自 Observable,这样我就可以使用常规 DI 结构继续处理。

这是守卫代码,提前致谢。

import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as jwt from 'jsonwebtoken';
import * as jwks from 'jwks-rsa';
import { Observable, bindNodeCallback } from 'rxjs';

interface jwtAccessToken {
  iss: string;
  sub: string;
  aud: string[];
  iat: number;
  exp: number;
  azp: string;
  scope: string;
}

/**
 * Guard validates Auth0 token and injects user into header
 */
@Injectable()
export class WsJwtGuard implements CanActivate {
  constructor(private configService: ConfigService) {}

  canActivate(context: ExecutionContext): boolean {
    try {
      const client = context.switchToWs().getClient();
      if (!client.handshake.auth || !client.handshake.auth.token) {
        throw new UnauthorizedException('Missing authorization header');
      }
      const token = client.handshake.auth.token.split(' ')[1];
      const jwksUri = `${this.configService.get(
        'AUTH0_DOMAIN'
      )}.well-known/jwks.json`;

      const jwksClient = jwks({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: jwksUri,
      });

      function getKey(header, callback) {
        jwksClient.getSigningKey(header.kid, function (err, key) {
          const signingKey = key.getPublicKey();
          callback(null, signingKey);
        });
      }

      // Believe this is what I need to make things work
      const verify: (...args: any[]) => Observable<jwtAccessToken> =
        bindNodeCallback(jwt.verify) as any;

      jwt.verify(
        token,
        getKey,
        {
          issuer: [this.configService.get('AUTH0_DOMAIN')], // DI works outside of callback
          audience: ['SOME_AUDIENCE'],
          algorithms: ['RS256'],
        },
        function (err, decoded: jwtAccessToken) {
          if (err) {
            throw new UnauthorizedException('Token is invalid or expired');
          }

          // Displays token
          console.log(`Decoded token: `, decoded);

          // Attempts to log an injected function
          console.log(this.configService.get('AUTH0_DOMAIN')); // DI fails inside of callback

          //
          // Want to check here for user record and inject into
          // headers but need to dependency inject repository
          //
        }
      );
      return true;
    } catch (err) {
      throw new UnauthorizedException('Token is invalid or expired');
    }
  }
}

I'm struggling to understand how dependency injection works with callbacks. I'm using NestJS to build a Socket.IO guard using the jsonwebtoken package and am receiving the following error when I try to access an injected function within the verify function's callback:

TypeError: Cannot read properties of undefined (reading 'configService')

The injected function works outside of the callback, and I understand that the callback is async, and that with this function I am unable to use async/await. I also must use a callback with this function, because I'm asynchronously pulling my keys using a JWKS client.

What I wish to do is inject my Typeorm repository, access my user entity, and inject it into the Socket.IO headers. I'm able to successfully inject a manually-created object, but I need to access the repository.

I found a suggestion on the NestJS forums that involves using bindNodeCallback to create an observable, but I couldn't get this to work, and was hoping somebody could suggest a solution, or help with the code to use bindNodeCallback to get me the decoded token from the Observable so I can continue processing using regular DI structure.

Here's the guard code, and thanks in advance.

import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as jwt from 'jsonwebtoken';
import * as jwks from 'jwks-rsa';
import { Observable, bindNodeCallback } from 'rxjs';

interface jwtAccessToken {
  iss: string;
  sub: string;
  aud: string[];
  iat: number;
  exp: number;
  azp: string;
  scope: string;
}

/**
 * Guard validates Auth0 token and injects user into header
 */
@Injectable()
export class WsJwtGuard implements CanActivate {
  constructor(private configService: ConfigService) {}

  canActivate(context: ExecutionContext): boolean {
    try {
      const client = context.switchToWs().getClient();
      if (!client.handshake.auth || !client.handshake.auth.token) {
        throw new UnauthorizedException('Missing authorization header');
      }
      const token = client.handshake.auth.token.split(' ')[1];
      const jwksUri = `${this.configService.get(
        'AUTH0_DOMAIN'
      )}.well-known/jwks.json`;

      const jwksClient = jwks({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: jwksUri,
      });

      function getKey(header, callback) {
        jwksClient.getSigningKey(header.kid, function (err, key) {
          const signingKey = key.getPublicKey();
          callback(null, signingKey);
        });
      }

      // Believe this is what I need to make things work
      const verify: (...args: any[]) => Observable<jwtAccessToken> =
        bindNodeCallback(jwt.verify) as any;

      jwt.verify(
        token,
        getKey,
        {
          issuer: [this.configService.get('AUTH0_DOMAIN')], // DI works outside of callback
          audience: ['SOME_AUDIENCE'],
          algorithms: ['RS256'],
        },
        function (err, decoded: jwtAccessToken) {
          if (err) {
            throw new UnauthorizedException('Token is invalid or expired');
          }

          // Displays token
          console.log(`Decoded token: `, decoded);

          // Attempts to log an injected function
          console.log(this.configService.get('AUTH0_DOMAIN')); // DI fails inside of callback

          //
          // Want to check here for user record and inject into
          // headers but need to dependency inject repository
          //
        }
      );
      return true;
    } catch (err) {
      throw new UnauthorizedException('Token is invalid or expired');
    }
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

相权↑美人 2025-01-17 21:32:00

我正在更新我的答案,以反映解决 JWT 解码问题的更好的库。我切换到“aws-jwt-verify”,它使用起来更简单——其中一些是从文档中抄袭的。

import { JwtRsaVerifier } from 'aws-jwt-verify';

try {
  const verifier = JwtRsaVerifier.create({
    issuer: "https://example.com/", // set this to the expected "iss" claim on your JWTs
    audience: "<audience>", // set this to the expected "aud" claim on your JWTs
    jwksUri: "https://example.com/.well-known/jwks.json", // set this to the JWKS uri from your OpenID configuration
  });
  return await verifier.verify(token);
} catch (err) {
  this.logger.log(`[decodeToken] ${err.message}`);
}

I'm updating my answer to reflect a better library for solving the JWT decoding problem. I switched to 'aws-jwt-verify' and it's much simpler to use -- some of this is cribbed from the docs.

import { JwtRsaVerifier } from 'aws-jwt-verify';

try {
  const verifier = JwtRsaVerifier.create({
    issuer: "https://example.com/", // set this to the expected "iss" claim on your JWTs
    audience: "<audience>", // set this to the expected "aud" claim on your JWTs
    jwksUri: "https://example.com/.well-known/jwks.json", // set this to the JWKS uri from your OpenID configuration
  });
  return await verifier.verify(token);
} catch (err) {
  this.logger.log(`[decodeToken] ${err.message}`);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文