基于Microsoft Identity Platform从Outlook加载项访问.NET Core REST API
你的猜测完全正确。你收到的错误:
Bearer error="invalid_token", error_description="The audience 'e1c50fba-abcd-4e63-9f54-xxxxxxxxxx' is invalid".
本质原因是Outlook加载项通过getAccessToken()获取的令牌,受众是加载项自身的Azure AD应用注册ID,而你的REST API验证时要求受众是自己的应用ID,因此触发无效令牌错误。必须使用On-Behalf-Of(OBO)委托流,由你的API将加载项的令牌兑换成针对自身API的有效令牌,这个流程可以通过Microsoft.Identity.Web轻松实现,且全程无需将兑换后的令牌返回给加载项,只需在API内部处理。
实现步骤
1. 确认Azure AD权限配置
确保你的REST API的Azure AD应用注册中,已添加Outlook加载项应用的access_as_user委托权限,并且该权限已完成管理员或用户同意。同时,要为你的REST API应用注册定义对应的自定义范围(比如access_as_user),用于OBO流兑换令牌时指定。
2. 调整API的认证服务配置
修改现有Program.cs中的认证配置,启用OBO流支持并配置下游API信息:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"), subscribeToJwtBearerMiddlewareDiagnosticsEvents: true) .EnableTokenAcquisitionToCallDownstreamApi() // 配置要兑换令牌的目标API(即你的REST API自身) .AddDownstreamApi("MyRestApi", builder.Configuration.GetSection("DownstreamApi")) // 保留令牌缓存,避免重复兑换 .AddInMemoryTokenCaches();
在appsettings.json中添加下游API的配置项:
"DownstreamApi": { "BaseUrl": "https://your-rest-api-base-url", "Scopes": "api://{your-rest-api-client-id}/access_as_user" }
将{your-rest-api-client-id}替换为你的REST API应用注册的客户端ID,Scopes值要和你在Azure AD中为API定义的范围一致。
3. 在API内部兑换并使用令牌
控制器内直接处理
在需要处理业务逻辑的控制器中,注入ITokenAcquisition服务来兑换令牌,并在内部使用该令牌进行权限验证或业务处理:
[ApiController] [Route("[controller]")] public class DemoController : ControllerBase { private readonly ITokenAcquisition _tokenAcquisition; private readonly IConfiguration _configuration; public DemoController(ITokenAcquisition tokenAcquisition, IConfiguration configuration) { _tokenAcquisition = tokenAcquisition; _configuration = configuration; } [HttpGet] [Authorize] public async Task<IActionResult> GetData() { // 兑换加载项的令牌为针对当前API的有效令牌 var apiScope = _configuration["DownstreamApi:Scopes"]; string validApiToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { apiScope }); // 后续业务逻辑可使用该令牌,比如调用内部服务、验证权限等 // 无需将此令牌返回给Outlook加载项 return Ok("处理完成"); } }
全局中间件透明处理(推荐)
如果希望所有请求进入控制器前自动完成令牌兑换,可自定义中间件,对加载项完全透明:
public class OboTokenMiddleware { private readonly RequestDelegate _next; private readonly ITokenAcquisition _tokenAcquisition; private readonly IConfiguration _configuration; public OboTokenMiddleware(RequestDelegate next, ITokenAcquisition tokenAcquisition, IConfiguration configuration) { _next = next; _tokenAcquisition = tokenAcquisition; _configuration = configuration; } public async Task InvokeAsync(HttpContext context) { // 获取目标API范围并兑换令牌 var apiScope = _configuration["DownstreamApi:Scopes"]; var validToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { apiScope }); // 替换请求头中的令牌为兑换后的有效令牌 context.Request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", validToken); await _next(context); } }
在Program.cs中注册中间件(需放在UseAuthentication之后、UseAuthorization之前):
app.UseAuthentication(); app.UseMiddleware<OboTokenMiddleware>(); app.UseAuthorization();
这样后续的授权逻辑和控制器就能直接使用有效令牌,全程对Outlook加载项无感知。
关键注意事项
- 令牌缓存(
AddInMemoryTokenCaches)会自动缓存兑换后的令牌,减少重复请求,提升性能;生产环境可替换为分布式缓存(如Redis)。 - 绝对不要将兑换后的令牌返回给Outlook加载项,所有兑换和使用逻辑必须在API内部完成。
- 确保Outlook加载项的应用注册已正确配置对REST API的
access_as_user权限,且已完成同意流程。
内容的提问来源于stack exchange,提问作者pgross

