Angular+NestJS+Auth0鉴权报错:Jwt Malformed排查求助
问题:Angular客户端发送JWT至NestJS服务器时触发'Jwt Malformed'错误
项目背景
使用Angular、NestJS和PostgreSQL开发全栈项目,通过Auth0实现认证(Authn)与授权(Authz)。数据库存储用户数据时保存Auth0返回的唯一sub ID,核心逻辑为:客户端登录后请求服务器数据时,服务器校验JWT中的sub与数据库中的sub是否匹配。
当前问题
客户端已获取并发送JWT至服务器,但NestJS的JwtAuthGuard抛出Jwt Malformed错误。手动在Postman中传入同一个JWT时请求正常,可完整获取数据,因此怀疑问题出在客户端代码。
相关代码
JwtAuthGuard代码
import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') { handleRequest( ...args: Parameters< InstanceType<ReturnType<typeof AuthGuard>>['handleRequest'] > ) { console.log(args); return super.handleRequest(...args); } }
报错信息
[ null, false, JsonWebTokenError: jwt malformed at module.exports [as verify] (C:\Projects\dnd-server\node_modules\jsonwebtoken\verify.js:70:17) at module.exports [as JwtVerifier] (C:\Projects\dnd-server\node_modules\passport-jwt\lib\verify_jwt.js:4:16) at C:\Projects\dnd-server\node_modules\passport-jwt\lib\strategy.js:104:25 at JwtStrategy.secretProvider [as _secretOrKeyProvider] (C:\Projects\dnd-server\node_modules\jwks-rsa\src\integrations\passport.js:42:14) at JwtStrategy.authenticate (C:\Projects\dnd-server\node_modules\passport-jwt\lib\strategy.js:99:10) at attempt (C:\Projects\dnd-server\node_modules\passport\lib\middleware\authenticate.js:378:16) at authenticate (C:\Projects\dnd-server\node_modules\passport\lib\middleware\authenticate.js:379:7) at C:\Projects\dnd-server\node_modules\@nestjs\passport\dist\auth.guard.js:88:3 at new Promise (<anonymous>) at C:\Projects\dnd-server\node_modules\@nestjs\passport\dist\auth.guard.js:80:83, ExecutionContextHost { args: [ [IncomingMessage], [ServerResponse], [Function: next] ], constructorRef: [class CharacterController], handler: [AsyncFunction: findOne], contextType: 'http', getRequest: [Function: getRequest], getResponse: [Function: getResponse], getNext: [Function: getNext] }, undefined ]
Auth策略代码(auth.strategy.ts)
import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import * as jwksRsa from 'jwks-rsa'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ // 从Authorization头提取JWT jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 验证token的issuer和audience issuer: `https://${process.env.AUTH0_DOMAIN}/`, audience: process.env.AUTH0_AUDIENCE, // 使用Auth0公钥验证签名 secretOrKeyProvider: jwksRsa.passportJwtSecret({ cache: true, rateLimit: true, jwksRequestsPerMinute: 5, jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`, }), algorithms: ['RS256'], }); } async validate(payload: any) { return { sub: payload.sub, // 返回sub声明 }; } }
角色控制器代码(character.controller.ts)
import { Body, Controller, Get, Param, Post, Req, UseGuards, } from '@nestjs/common'; import { CharacterService } from './character.service'; import { Character } from './character.model'; import { JwtAuthGuard } from 'src/auth/auth.guard'; @Controller('character') export class CharacterController { constructor(private readonly characterService: CharacterService) {} @Post() async create(@Body() character: Character): Promise<Character> { const createdCharacter = await this.characterService.create(character); console.log('Character created successfully:', createdCharacter); return createdCharacter; } @Get(':id') @UseGuards(JwtAuthGuard) async findOne(@Param('id') id: string, @Req() req): Promise<Character> { const sub = req.user?.sub; if (!sub) { console.error("The 'sub' parameter is undefined or null."); throw new Error("Invalid 'sub' parameter"); } console.log('Server SUB:', sub); const character = await this.characterService.findOne(+id); if (!character) { console.error(`Character with ID ${id} not found.`); throw new Error('Character not found'); } if (character.sub !== sub) { console.error( `Server SUB ${sub} does not match character sub ${character.sub}.`, ); throw new Error('Unauthorized access'); } return character; } }
Angular客户端代码(creator.component.ts)
saveFormDataToFile() { const data = this.characterCreationForm.value; this.http.post("http://localhost:3000/character", data).subscribe( (response) => { console.log("Form data saved successfully:", response); }, (error) => { console.error("Error saving form data:", error); } ); } retrieveFormDataFromServer(id: number) { const authToken = this.auth.idTokenClaims$; authToken.subscribe((token) => { if (token) { const headers = new HttpHeaders().set( "Authorization", `Bearer ${token.__raw}` ); console.log("FULL TOKEN:", token); console.log("Access token retrieved successfully:", token.__raw); this.http .get(`http://localhost:3000/character/${id}`, { headers }) .subscribe( (response: any) => { console.log("Form data retrieved successfully:", response); this.characterCreationForm.patchValue(response); }, (error) => { console.error("Error retrieving form data:", error); } ); } else { console.error("Error retrieving access token."); } }); }
排查与解决方案
1. 替换ID Token为Access Token
Auth0的ID Token仅用于客户端身份验证,访问API必须使用Access Token。当前客户端代码使用idTokenClaims$获取的是ID Token,这是核心问题。修改客户端代码,改用getAccessTokenSilently()获取Access Token:
async retrieveFormDataFromServer(id: number) { try { // 获取用于API访问的Access Token const accessToken = await this.auth.getAccessTokenSilently(); const headers = new HttpHeaders().set( "Authorization", `Bearer ${accessToken}` ); this.http.get(`http://localhost:3000/character/${id}`, { headers }) .subscribe( (response: any) => { console.log("Form data retrieved successfully:", response); this.characterCreationForm.patchValue(response); }, (error) => { console.error("Error retrieving form data:", error); } ); } catch (error) { console.error("获取Access Token失败:", error); } }
2. 验证JWT格式与请求头
- 检查客户端打印的
token.__raw(或修改后的accessToken)是否为标准三段式JWT字符串(header.payload.signature),无多余空格或特殊字符。 - 确认请求头格式正确:
Authorization: Bearer <token>,其中Bearer后必须有一个空格。
3. 配置NestJS CORS确保头传递
确保NestJS服务器允许Angular客户端传递Authorization头,在main.ts中添加CORS配置:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 配置CORS app.enableCors({ origin: "http://localhost:4200", // 替换为你的Angular端口 allowedHeaders: ["Authorization", "Content-Type"], credentials: true, }); await app.listen(3000); } bootstrap();
4. 验证Auth0配置一致性
确保NestJS的AUTH0_AUDIENCE环境变量与Auth0中API的标识符一致,AUTH0_DOMAIN与Auth0域名一致,避免因配置不匹配导致token验证失败。
内容的提问来源于stack exchange,提问作者Andriy Panasiuk
相关产品推荐
相关产品推荐

