You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

后端验证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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 21:57:42