AzureAD身份验证: 受众验证失败。受众不匹配

huangapple go评论72阅读模式
英文:

AzureAD Authentication: Audience validation failed. Audiences Did not match

问题

我正在使用托管身份在我的调用API中从AzureAD获取访问令牌。这个用户分配的托管身份在被调用API的应用程序注册清单中定义了一个应用程序角色。令牌中包含了应用程序角色。到目前为止,一切都很好(我认为是这样的)。

当我将令牌附加到请求并调用预期的API时,我收到以下消息:

IDX10214:受众验证失败。受众:'{被调用API应用程序注册的客户端ID}'。未匹配:validationParameters.ValidAudience:'api://{被调用API应用程序注册的客户端ID}'或validationParameters.ValidAudiences:'null'。

令牌上的受众与内置令牌验证所用的ValidAudience进行比较的内容之间的区别是前缀"api://"。在Azure门户中定义的应用程序注册的应用程序URI确实是"api://{被调用API应用程序注册的客户端ID}"

在生成我的令牌时,我尝试了许多不同的请求上下文的变体...将"api://"前缀添加到GUID,将"/.default"附加到应用程序URI,但无法使令牌被接受为有效。

这是我在被调用应用程序上的配置部分,用于授权所呈现的令牌:

{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "与应用注册应用程序ID匹配的GUID",
"TenantId": "我的租户ID",
"Audience": "api://与应用注册应用程序ID匹配的GUID"
}
}

这是我的Program.cs:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Microsoft.IdentityModel.Logging;

var builder = WebApplication.CreateBuilder(args);

IdentityModelEventSource.ShowPII = true;

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services
    .AddApplicationInsightsTelemetry()
    .AddHealthChecks()
    .AddApplicationInsightsPublisher(saveDetailedReport: true);

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.MapHealthChecks("/healthz");

app.Run();

这是我的控制器:

using theModelNamespace.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace someMoreNamespace.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class NamesController : ControllerBase
    {
        [HttpGet("ping")]
        //[AllowAnonymous]
        public IActionResult PingOnly()
        {
            return Ok("Alive");
        }

        [HttpGet()]
        //[Authorize(Roles = "Api.Read,Api.ReadWrite,Api.OtherUserApp")]
        public async Task<IActionResult> GetNames()
        {
            AuthenticateResult authResult;
            try
            {
                authResult = await HttpContext.AuthenticateAsync("Bearer");
            }
            catch (Exception ex)
            {
                var innerException = ex.InnerException != null ? ex.InnerException.Message : String.Empty;
                var responseString = $"Error occurred in authentication: {ex.Message} {innerException}.";
                return StatusCode(500, responseString);
            }
            try
            { 
                HttpContext.ValidateAppRole("Api.OtherUserApp");
                return Ok(Data.NameList);
            }
            catch (Exception ex)
            {
                var innerException = ex.InnerException != null ? ex.InnerException.Message : String.Empty;
                var authResults = (authResult != null && authResult.Principal != null) ? $"succeeded: {authResult.Succeeded}, identityName: {authResult.Principal.Identity?.Name}, {authResult.Principal.Claims?.Select(x => $"{x.Type}: {x.Value}")}" : string.Empty;
                authResults = authResults == String.Empty && authResult.Failure != null ? authResult.Failure.Message : authResults;
                var claimContents = HttpContext != null && HttpContext.User != null ? String.Join(',', HttpContext.User.Claims.Select(x => $"{x.Type}: {x.Value}")) : String.Empty;
                var responseString = $"Error occurred in validation: {ex.Message} {innerException}. \n\nClaim contents: {claimContents}\n\nAuthResults: {authResults}";
                return StatusCode(500, responseString);
            }
        }

        [HttpPost()]
        //[Authorize(Roles = "Api.ReadWrite")]
        public IActionResult PostName([FromBody] NamesModel nameModel)
        {
            Data.NameList.Add(nameModel);
            return Ok(Data.NameList);
        }

        [HttpGet("Unreachable")]
        //[Authorize(Roles = "Api.InvalidScope")]
        public IActionResult UnreachableName([FromBody] NamesModel nameModel)
        {
            Data.NameList.Add(nameModel);
            return Ok(Data.NameList);
        }
    }
}

我已经注释掉了授权属性,并添加了HttpContext.AuthenticateAsync("Bearer")以进行故障排除,以便我可以看到我在帖子开头列出的认证结果的输出。

我已经检查了令牌,"aud"声明确实是被调用API应用程序注册的客户端ID,而不带前缀"api://"。匹配所需的方式(角色:["Api.OtherUserApp"])在令牌中似乎包含在其中。

匿名调用按预期工作得很好。只有调用AuthenticateAsync的get端点存在问题。

我错过了什么,以使被调用API接受令牌?

英文:

I'm getting an access token from AzureAD using managed identity in my calling API. This user-assigned managed identity has an app role defined by the manifest of the called-API's app registration assigned to it. The token has the app role in its contents. So far, so good (I think.)

When I attach the token to a request and call the intended API, I get the following message:

> IDX10214: Audience validation failed. Audiences: '{clientId of called API App registration}'. Did not match: validationParameters.ValidAudience: 'api://{clientId of called API App registration}' or validationParameters.ValidAudiences: 'null'.

The difference between the audience on the token and what the built-in token validation is using as the ValidAudience to compare it to is the preceding "api://". The application URI of the app registration defined in Azure Portal is indeed "api://{clientId of called API App registration}"

I have tried many different variations of request contexts when generating my token... prefixing "api://" to the guid, appending "/.default" to the Application URI, but cannot get the token to be accepted as valid.

This is the configuration section I have on my called application to authorize the token presented:

{
&quot;AzureAd&quot;: {
&quot;Instance&quot;: &quot;https://login.microsoftonline.com/&quot;,
&quot;ClientId&quot;: &quot;the Guid matching the app registration Application ID&quot;,
&quot;TenantId&quot;: &quot;my tenant id&quot;,
&quot;Audience&quot;: &quot;api://{the Guid matching the app registration Application ID}&quot;
}
}

This is my Program.cs:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Microsoft.IdentityModel.Logging;
var builder = WebApplication.CreateBuilder(args);
IdentityModelEventSource.ShowPII = true;
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection(&quot;AzureAd&quot;));
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services
.AddApplicationInsightsTelemetry()
.AddHealthChecks()
.AddApplicationInsightsPublisher(saveDetailedReport: true);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHealthChecks(&quot;/healthz&quot;);
app.Run();

This is my controller:

using theModelNamespace.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace someMoreNamespace.Controllers
{
[Route(&quot;api/[controller]&quot;)]
[ApiController]
public class NamesController : ControllerBase
{
[HttpGet(&quot;ping&quot;)]
//[AllowAnonymous]
public IActionResult PingOnly()
{
return Ok(&quot;Alive&quot;);
}
[HttpGet()]
//[Authorize(Roles = &quot;Api.Read,Api.ReadWrite,Api.OtherUserApp&quot;)]
public async Task&lt;IActionResult&gt; GetNames()
{
AuthenticateResult authResult;
try
{
authResult = await HttpContext.AuthenticateAsync(&quot;Bearer&quot;);
}
catch (Exception ex)
{
var innerException = ex.InnerException != null ? ex.InnerException.Message : String.Empty;
var responseString = $&quot;Error occurred in authentication: {ex.Message} {innerException}.&quot;;
return StatusCode(500, responseString);
}
try 
{ 
HttpContext.ValidateAppRole(&quot;Api.OtherUserApp&quot;);
return Ok(Data.NameList);
}
catch (Exception ex)
{
var innerException = ex.InnerException != null ? ex.InnerException.Message : String.Empty;
var authResults = (authResult != null &amp;&amp; authResult.Principal != null) ? $&quot;succeeded: {authResult.Succeeded}, identityName: {authResult.Principal.Identity?.Name}, {authResult.Principal.Claims?.Select(x =&gt; $&quot;{x.Type}: {x.Value}&quot;)}&quot; : string.Empty;
authResults = authResults == String.Empty &amp;&amp; authResult.Failure != null ? authResult.Failure.Message : authResults;
var claimContents = HttpContext != null &amp;&amp; HttpContext.User != null ? String.Join(&#39;,&#39;, HttpContext.User.Claims.Select(x =&gt; $&quot;{x.Type}: {x.Value}&quot;)) : String.Empty;
var responseString = $&quot;Error occurred in validation: {ex.Message} {innerException}. \n\nClaim contents: {claimContents}\n\nAuthResults: {authResults}&quot;;
return StatusCode(500, responseString);
}
}
[HttpPost()]
//[Authorize(Roles = &quot;Api.ReadWrite&quot;)]
public IActionResult PostName([FromBody] NamesModel nameModel)
{
Data.NameList.Add(nameModel);
return Ok(Data.NameList);
}
[HttpGet(&quot;Unreachable&quot;)]
//[Authorize(Roles = &quot;Api.InvalidScope&quot;)]
public IActionResult UnreachableName([FromBody] NamesModel nameModel)
{
Data.NameList.Add(nameModel);
return Ok(Data.NameList);
}
}
}

I have the authorize attributes commented out and the HttpContext.AuthenticateAsync("Bearer") added in for troubleshooting and so I can see the output of the authentication result I listed at the beginning of the post.

I've inspected the token, and the "aud" claim is indeed the clientId of the app registration of the called API, and is not prefixed with "api://" The role I need appears to be contained the expected way (roles: [ "Api.OtherUserApp" )] in the token.

The anonymous calls work fine as expected. It is only the get endpoint which calls AuthenticateAsync which has an issue.

What am I missing here to get the token to be accepted by the called API?

答案1

得分: 1

我尝试在我的环境中复制相同的情况。

我收到了相同的错误:

SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: '50065xxxxx1e6fbd2ed06e'. Did not match: validationParameters.ValidAudience: 'api://xxxxxx1xx06e' or validationParameters.ValidAudiences: 'null'.

AzureAD身份验证: 受众验证失败。受众不匹配

正如错误所说,受众与我们在令牌中获得的受众不匹配。确保受众中的值被记录,并检查它是否等于客户端ID。

请注意,我们可以有的有效受众要么是客户端ID,要么是应用程序ID URI。

这里我得到了一个值为应用程序ID或客户端ID的受众,它与我的代码请求不匹配。

因为你提供了应用程序ID URI,即 api:// 作为受众,这是无效的。`所以这里应该提供正确的ClientId。

ValidAudiences = new List<string>
{
“CLIENTID”,
“APPID URI”
}

Appsettings.json

{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "testtenant.onmicrosoft.com",
"ClientId": "xxxxxx",
"TenantId": "xxxxxxxxxxxx",
"ClientSecret": "xxxxx",
"Audience": "<clientId>",
"ClientCertificates": [],
"CallbackPath": "/signin-oidc"
},
"DownstreamApi": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "https://graph.microsoft.com/.default"
},
}

AzureAD身份验证: 受众验证失败。受众不匹配


在这里,V2终结点应用程序的范围可以暴露在 api:///access_as_user 或 api://<clientId>/<scope value> 用于您的 Web API。

  • 确保您的 accessTokenAcceptedVersion 是 V2 终结点发行者的2。

AzureAD身份验证: 受众验证失败。受众不匹配

对于V1,范围是 <App ID URI>/.default

在我的发行者使用V1终结点的情况下

AzureAD身份验证: 受众验证失败。受众不匹配

在这种情况下,accessTokenAcceptedVersionnull或1


我尝试通过我的控制器在我的API中获取用户显示名称,使用以下代码。

HomeController:

[Authorize]
public async Task<IActionResult> Index()
{
    var user = await _graphServiceClient.Me.Request().GetAsync();
    ViewData["ApiResult"] = user.DisplayName;
    ViewData["Givenname"] = user.GivenName;
    return View();
}

我成功运行了应用程序并调用了我的API端点而没有错误。

AzureAD身份验证: 受众验证失败。受众不匹配

英文:

I tried to reproduce the same in my environment.

I received the same error:

SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: &#39;50065xxxxx1e6fbd2ed06e&#39;. Did not match: validationParameters.ValidAudience: &#39;api://xxxxxx1xx06e&#39; or validationParameters.ValidAudiences: &#39;null&#39;.

AzureAD身份验证: 受众验证失败。受众不匹配

Here as the error says , audiences did not match the one we got in token.
Make sure the value in the audience is noted and check the same if it is equal to clientId or not.

> Note that ,The valid audiences we can have is either clientId or AppId URI

Here I am getting an audience of value applicationId or clientId in error which it is not matching my code requested .

as you gave AppId URI i.e; api://<clientId> ,for audience ,it is invalid. So the correct one to be given here is ClientId.

ValidAudiences = new List&lt;string&gt; 
{
“CLIENTID”,
“APPID URI”
}

Appsettings.json

{
&quot;AzureAd&quot;: {
&quot;Instance&quot;: &quot;https://login.microsoftonline.com/&quot;,
&quot;Domain&quot;: &quot;testtenant.onmicrosoft.com&quot;,
&quot;ClientId&quot;: &quot;xxxxxx&quot;,
&quot;TenantId&quot;: &quot;xxxxxxxxxxxx&quot;,
&quot;ClientSecret&quot;: &quot;xxxxx&quot;,
&quot;Audience&quot;: &quot;&lt;clientId&gt;&quot;,
&quot;ClientCertificates&quot;: [
],
&quot;CallbackPath&quot;: &quot;/signin-oidc&quot;
},
&quot;DownstreamApi&quot;: {
&quot;BaseUrl&quot;: &quot;https://graph.microsoft.com/v1.0&quot;,
//&quot;Scopes&quot;: &quot;api://&lt;clientId&gt;/.default&quot;
&quot;Scopes&quot;: &quot;https://graph.microsoft.com/.default&quot;
},

AzureAD身份验证: 受众验证失败。受众不匹配


Here a scope for a V2 endpoint application can be exposed at api://<clientId>/access_as_user or api://&lt;clientId&gt;/&lt;scope value&gt; for your webapi .

  • Make sure your accessTokenAcceptedVersion is 2 for v2 endpoint issuer

AzureAD身份验证: 受众验证失败。受众不匹配

For v1 scope is &lt;App ID URI&gt;/.default,

Here when my issuer has v1 endpoint

AzureAD身份验证: 受众验证失败。受众不匹配

In that case accessTokenAcceptedVersion is null or 1


I tried to get the user display name, through my api using below code in my controller.

HomeController:

[Authorize]
public async Task&lt;IActionResult&gt; Index()
{
var user = await _graphServiceClient.Me.Request().GetAsync();
ViewData[&quot;ApiResult&quot;] = user.DisplayName;
ViewData[&quot;Givenname&quot;] = user.GivenName;
return View();
}

I could run the application successfully and call my API enpoint without error.

AzureAD身份验证: 受众验证失败。受众不匹配

huangapple
  • 本文由 发表于 2023年2月7日 02:15:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75365116.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定