如何通过Azure AD权限范围与oidc-client.js获取用户信息及访问令牌?
你遇到的核心问题其实是access_token的受众(aud字段)不匹配,这是Azure AD结合OIDC协议使用时的常见细节,我来帮你拆解清楚:
问题根源
当你在scope中同时指定自定义API范围(api://${clientId}/access_user_data)和openid时,Azure AD返回的access_token是专为你的自定义API生成的——它的受众是你的API的client ID,而非Microsoft Graph。而https://graph.microsoft.com/oidc/userinfo端点只接受受众为Graph的access_token,用自定义API的token请求自然会返回401。
单独设置scope: "openid"时,返回的token受众匹配Graph,或者id_token里已经包含足够信息,所以getUser()能正常工作,但此时你又缺失了访问自定义API的权限。
解决方案
根据你的需求,有两种可行的解决思路:
思路1:直接从id_token获取用户信息,跳过Graph userinfo端点
OIDC的openid范围要求id_token必须包含用户的基本身份信息(比如sub、name、email、preferred_username等),这些信息已经存在于user.profile中,完全不需要额外调用userinfo端点。
调整你的配置如下:
var settings: UserManagerSettings = { authority: `https://login.microsoftonline.com/${tenantId}`, client_id: clientId, redirect_uri: "http://localhost:3000/authcallback", post_logout_redirect_uri: "http://localhost:3000/authcallback", response_type: "token id_token", scope: `api://${clientId}/access_user_data openid`, popup_redirect_uri: "http://localhost:3000/authcallback", silent_redirect_uri: "http://localhost:3000/authcallback", automaticSilentRenew: true, loadUserInfo: false, // 关闭自动调用userinfo端点 metadata: { authorization_endpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`, issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`, jwks_uri: `https://login.microsoftonline.com/${tenantId}/discovery/v2.0/keys` // 移除userinfo_endpoint配置,因为我们不再需要它 } };
之后调用getUser()时,直接从user.profile里拿用户信息即可:
const user = await userManager.getUser(); console.log("用户名:", user.profile.name); console.log("邮箱:", user.profile.email);
思路2:获取两个不同的access_token(分别用于自定义API和Graph)
如果你确实需要调用Graph的userinfo端点获取更多用户信息,就得获取一个受众为Microsoft Graph的access_token。由于implicit flow一次只能返回一个token,你可以通过静默登录来获取第二个token:
- 先在Azure门户中给你的应用添加Microsoft Graph的
User.Read委托权限,并完成管理员同意。 - 保留原有针对自定义API的
UserManager,再新增一个专门用于Graph的实例:
// 原有针对自定义API的UserManager const apiUserManager = new UserManager(apiSettings); // 新增针对Graph的UserManager配置 const graphSettings: UserManagerSettings = { authority: `https://login.microsoftonline.com/${tenantId}`, client_id: clientId, redirect_uri: "http://localhost:3000/authcallback", response_type: "token", scope: "https://graph.microsoft.com/User.Read openid", silent_redirect_uri: "http://localhost:3000/authcallback", automaticSilentRenew: false, loadUserInfo: true, metadata: { userinfo_endpoint: "https://graph.microsoft.com/oidc/userinfo", authorization_endpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`, issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`, jwks_uri: `https://login.microsoftonline.com/${tenantId}/discovery/v2.0/keys` } }; const graphUserManager = new UserManager(graphSettings);
- 在首次登录成功后,调用
graphUserManager.signinSilent()获取Graph的token,此时再调用graphUserManager.getUser()就能正常访问userinfo端点了。
额外提示
- 可以用jwt.ms解析token,确认
aud字段是否匹配目标服务,这是排查token权限问题的常用方法。 - 如果不需要Graph的额外信息,思路1是最简洁的方案——毕竟id_token已经包含了OIDC标准要求的所有基本用户信息。
内容的提问来源于stack exchange,提问作者Konzy262

