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

Blazor WASM托管方案:如何用客户端令牌在服务端调用MS Graph API?

Blazor WASM客户端登录后,ASP.NET Core Web API调用MS Graph API的配置方案及问题解决

客户端应用配置

客户端 - Program.cs

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions);
});

builder.Services
    .AddHttpClient(HttpClients.SERVER_API, client =>
    {
        client.BaseAddress = new Uri(appSettings.ServerApi.Url);
    })
    // 配置客户端为所有发送到授权URL的请求,在'Authorization'头中添加JWT令牌
    .AddHttpMessageHandler(sp =>
        sp.GetRequiredService<AuthorizationMessageHandler>()
            .ConfigureHandler(
                authorizedUrls: [ appSettings.ServerApi.Url ],
                scopes: [ appSettings.ServerApi.AccessScope ]
            ));

客户端 - appsettings.json

"AzureAd": {
    "Authentication": {
        "Authority": "https://login.microsoftonline.com/你的租户ID",
        "ClientId": "客户端应用ID"
    },
    "DefaultAccessTokenScopes": [
        "api://服务端应用ID/API.Access",
        "https://graph.microsoft.com/User.Read"
    ]
}

服务端应用配置

服务端 - Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration)
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApis:MicrosoftGraph"))
    .AddInMemoryTokenCaches();

服务端 - appsettings.json

"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "你的租户域名.onmicrosoft.com",
    "TenantId": "你的租户ID",
    "ClientId": "服务端应用ID",
    "ClientSecret": "服务端应用密钥",
    "Scopes": "API.Access",
    "CallbackPath": "/signin-oidc"
},
"DownstreamApis": {
    "MicrosoftGraph": {
        "BaseUrl": "https://graph.microsoft.com/v1.0",
        "Scopes": "User.Read"
    }
},

服务端 - 调用Graph API示例

using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;
using Microsoft.Identity.Web;

namespace ChatPortal.Server.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
    private readonly GraphServiceClient _graphClient;

    public TestController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }

    [HttpGet("graph")]
    public async Task<IActionResult> GraphTest()
    {
        var user = await _graphClient.Me.GetAsync();

        return Ok(user);
    }
}

Azure门户配置

客户端应用注册

  • API权限:
    • 服务端应用(MyServerApp):API.Access,类型:委托权限,状态:已授予管理员同意
    • Microsoft Graph:User.Read,类型:委托权限,状态:已授予管理员同意

服务端应用注册

  • 暴露API:
    • 添加范围:api://服务端应用ID/API.Access
    • 授权客户端应用:添加客户端应用ID,授予对上述范围的访问权限

遇到的问题及解决方法

问题1:移除客户端DefaultAccessTokenScopes中的https://graph.microsoft.com/User.Read后调用Graph报错

错误信息:

[12:47:58 ERR] 请求执行过程中发生未处理异常。
Microsoft.Identity.Web.MicrosoftIdentityWebChallengeUserException: IDW10502: 因需要用户交互验证,抛出了MsalUiRequiredException。
 ---&gt; MSAL.NetCore.4.66.1.0.MsalUiRequiredException:
        ErrorCode: invalid_grant
Microsoft.Identity.Client.MsalUiRequiredException: AADSTS65001: 用户或管理员未同意使用ID为'你的应用ID'、名称为'ChatPortal.Server'的应用。请为此用户和资源发送交互式授权请求。

原因:服务端默认尝试用自身身份获取Graph令牌,未使用客户端传递的用户令牌走委托(OBO)流程,导致缺少用户授权。

问题2:保留客户端DefaultAccessTokenScopes中的User.Read后报错

错误信息:

请求URL: https://login.microsoftonline.com/你的租户ID/oauth2/v2.0/token
无效请求: AADSTS28000: 输入参数scope的值无效,因为它包含多个资源。Scope api://服务端应用ID/API.Access https://graph.microsoft.com/User.Read openid profile offline_access 无效。

原因:Azure AD v2.0令牌端点不允许单次请求包含多个资源的scope,客户端不能同时请求服务端API和Graph的权限。


正确解决方案

1. 客户端配置调整

客户端仅请求服务端API的scope,移除Graph相关scope:

"AzureAd": {
    "Authentication": {
        "Authority": "https://login.microsoftonline.com/你的租户ID",
        "ClientId": "客户端应用ID"
    },
    "DefaultAccessTokenScopes": [
        "api://服务端应用ID/API.Access"
    ]
}

2. 服务端启用OBO流程

服务端需配置为基于客户端传递的用户令牌,通过OBO流程获取Graph访问令牌,现有Program.cs配置已支持该流程,只需补充服务端应用的Graph权限:

  • 在Azure门户的服务端应用注册中,添加Microsoft Graph的User.Read委托权限,并授予管理员同意。

3. 控制器调用Graph API

直接注入GraphServiceClient调用即可,Microsoft.Identity.Web会自动完成OBO令牌获取:

[HttpGet("graph")]
public async Task<IActionResult> GraphTest()
{
    try
    {
        var user = await _graphClient.Me.GetAsync();
        return Ok(new { 显示名称 = user.DisplayName, 邮箱 = user.Mail });
    }
    catch (Exception ex)
    {
        return BadRequest(ex.Message);
    }
}

内容的提问来源于stack exchange,提问作者kglundgren

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:29:51