使用 NestJS、JWT 和 Socket.IO 进行依赖注入和回调
我正在努力理解依赖注入如何与回调一起工作。我正在使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我正在更新我的答案,以反映解决 JWT 解码问题的更好的库。我切换到“aws-jwt-verify”,它使用起来更简单——其中一些是从文档中抄袭的。
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.