后端验证Microsoft Graph API的Azure访问令牌问题求助
排查Azure AD访问令牌后端验证"Invalid signature"问题
问题背景
前端通过MSAL.js配置Azure AD应用,获取了用于Microsoft Graph API的访问令牌,将其作为Bearer Token传入后端API时,后端用JWT验证出现"Invalid signature"错误,且已确认jwkToPem生成的公钥正确。
核心排查点
- 令牌受众不匹配:前端请求的是Graph API的令牌,其
aud字段为https://graph.microsoft.com,但后端API的验证受众应为自身的客户端ID。若误将Graph令牌传给后端,签名验证必然失败——因为Graph的签名密钥与后端应用的密钥不匹配。 - JWKS端点获取错误:确保后端获取JWKS的元数据URL是对应租户的通用端点(
https://login.microsoftonline.com/{tenant_id}/.well-known/openid-configuration),而非带前端appid参数的特定端点。错误的端点会拿到不匹配的公钥集合。 - 令牌类型错误:确认传入后端的是
access_token而非id_token。id_token用于用户身份认证,签名密钥和受众均与后端API所需的access_token不同。 - 验证参数缺失:仅验证签名不足以确保令牌合法,需同时校验
iss(签发者)、aud(受众)、签名算法(Azure AD默认用RS256)等字段,部分参数不匹配可能导致错误信息显示为签名无效。
正确的后端验证实现
import jwt, { JwtPayload } from 'jsonwebtoken'; import jwkToPem from 'jwk-to-pem'; import axios from 'axios'; // 替换为你的租户ID和后端API的客户端ID const TENANT_ID = 'your-tenant-id'; const API_CLIENT_ID = 'your-backend-api-client-id'; async function getOpenIdConfig() { const response = await axios.get(`https://login.microsoftonline.com/${TENANT_ID}/.well-known/openid-configuration`); return response.data; } async function getJwks(uri: string) { const response = await axios.get(uri); return response.data.keys; } export async function verifyAccessToken(accessToken: string): Promise<JwtPayload | null> { try { // 解码令牌头获取kid const decodedToken = jwt.decode(accessToken, { complete: true }); if (!decodedToken || !decodedToken.header?.kid) { throw new Error('Invalid token structure'); } const kid = decodedToken.header.kid; // 获取租户的OpenID配置和JWKS密钥集 const openIdConfig = await getOpenIdConfig(); const jwks = await getJwks(openIdConfig.jwks_uri); // 根据kid匹配对应公钥 const targetKey = jwks.find((key: any) => key.kid === kid); if (!targetKey) { throw new Error('No matching public key found for token kid'); } // 转换为公钥PEM格式 const pem = jwkToPem(targetKey); // 验证令牌并校验关键字段 const payload = jwt.verify(accessToken, pem, { issuer: `https://login.microsoftonline.com/${TENANT_ID}/v2.0`, audience: API_CLIENT_ID, algorithms: ['RS256'] // Azure AD固定使用RS256签名算法 }) as JwtPayload; return payload; } catch (err) { console.error('Token verification failed:', err); return null; } }
前端配置调整
前端需请求后端API的专属scope,而非Graph API的scope,确保令牌受众匹配后端客户端ID:
export const loginRequest = { scopes: [`api://${API_CLIENT_ID}/access_as_user`] // 替换为你的后端API定义的scope };
内容的提问来源于stack exchange,提问作者James Jeramiah
相关产品推荐
相关产品推荐

