Created
October 20, 2022 08:52
-
-
Save myesn/e19ba8fce32a145a4147b5844926ce96 to your computer and use it in GitHub Desktop.
NestJS Logto Auth Guard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Injectable, | |
CanActivate, | |
ExecutionContext, | |
HttpStatus, | |
BadRequestException, | |
UnauthorizedException, | |
} from '@nestjs/common'; | |
import { Request } from 'express'; | |
import { ConfigService } from '@nestjs/config'; | |
import axios from 'axios'; | |
import { createRemoteJWKSet, jwtVerify } from 'jose'; | |
@Injectable() | |
export class LogtoAuthGuard implements CanActivate { | |
private discoveryCache: DiscoveryResponseData; | |
constructor(private readonly configService: ConfigService) {} | |
async canActivate(context: ExecutionContext): Promise<boolean> { | |
const request = context.switchToHttp().getRequest() as Request; | |
await this.verifyAuthFromRequest(request); | |
return true; | |
} | |
private async verifyAuthFromRequest(request: Request) { | |
// 从请求头中获取令牌 | |
const token = this.extractBearerTokenFromHeaders(request); | |
if (!this.discoveryCache) { | |
this.discoveryCache = await this.fetchDiscovery(); | |
} | |
try { | |
const { payload } = await jwtVerify( | |
token, | |
// 使用我们从 Logto OIDC 配置信息中获取的 公共 jwks_uri 提取一个公钥集。 | |
createRemoteJWKSet(new URL(this.discoveryCache.jwks_uri)), | |
{ | |
// 令牌应由 Logto 服务器发行 | |
issuer: this.discoveryCache.issuer, | |
// 该令牌的目标受众应为当前被请求的 API 地址 | |
audience: 'http://localhost:8080', // resource | |
}, | |
); | |
// 提取 payload 信息 | |
request.user = { id: payload.sub }; | |
} catch (e) { | |
throw new UnauthorizedException('令牌过期'); | |
} | |
} | |
private async fetchDiscovery() { | |
const logtoConfig = this.configService.get('logto'); | |
const discoveryUrl = `https://${logtoConfig.domain}/oidc/.well-known/openid-configuration`; | |
const response = await axios.get<DiscoveryResponseData>(discoveryUrl); | |
if (response.status !== HttpStatus.OK) { | |
throw new BadRequestException(`请求 ${discoveryUrl} 失败`); | |
} | |
return response.data; | |
} | |
// 提取 access_token | |
private extractBearerTokenFromHeaders(request: Request) { | |
// // //https://docs.logto.io/zh-cn/docs/recipes/protect-your-api/node/#%E4%BB%8E%E8%AF%B7%E6%B1%82%E5%A4%B4%E4%B8%AD%E6%8F%90%E5%8F%96%E4%BB%A4%E7%89%8C | |
const authorization = request.get('Authorization'); | |
const bearerTokenIdentifier = 'Bearer'; | |
if (!authorization) { | |
throw new UnauthorizedException('auth.authorization_header_missing'); | |
} | |
if (!authorization.startsWith(bearerTokenIdentifier)) { | |
throw new UnauthorizedException( | |
'auth.authorization_token_type_not_supported', | |
); | |
} | |
return authorization.slice(bearerTokenIdentifier.length + 1); | |
} | |
} | |
interface DiscoveryResponseData { | |
jwks_uri: string; | |
issuer: string; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment