Search code examples
c#azureasp.net-coreazure-managed-identity

AzureAD Authentication: Audience validation failed. Audiences Did not match


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:

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

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("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();

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("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);
        }
    }
}

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?


Solution

  • I tried to reproduce the same in my environment.

    I received the same error:

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

    enter image description here

    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:// ,for audience ,it is invalid. So the correct one to be given here is 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": "api://<clientId>/.default"
          "Scopes": "https://graph.microsoft.com/.default"
        },
    

    enter image description here


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

    • Make sure your accessTokenAcceptedVersion is 2 for v2 endpoint issuer

    enter image description here

    For v1 scope is <App ID URI>/.default,

    Here when my issuer has v1 endpoint

    enter image description here

    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<IActionResult> Index()
     {
        var user = await _graphServiceClient.Me.Request().GetAsync();
           ViewData["ApiResult"] = user.DisplayName;
         ViewData["Givenname"] = user.GivenName;
         return View();
         }
    

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

    enter image description here