question

MaccariFabio-7507 avatar image
0 Votes"
MaccariFabio-7507 asked tinywang-msft commented

AzureAD, Client confidential app calling webapi with a custom Application ID URI, returns 401

I'm trying to develop an API which can be called from different web apps (both in client confidential or with the user token).

If I call the api with a client confidential app, using the default scope (api://[APIclientId]/.default), everything works.

But If I specify a custom Application ID URI for the API app registration (like: api://myapi.iss.it), and I set the scope to api://myapi.iss.it/.default, I get HTTP401 from the webapp.

This is the method to retrieve the token for the webapp to call the api:

 private async Task PrepareAuthenticatedClient()
     {
         IConfidentialClientApplication app;
         string AURY = String.Format(CultureInfo.InvariantCulture, _config["AzureAd:Instance"] + "{0}", _config["AzureAd:TenantId"]);
         app = ConfidentialClientApplicationBuilder.Create(_config["AzureAd:ClientId"])
             .WithClientSecret(_config["AzureAd:ClientSecret"])
             .WithAuthority(new Uri(AURY))
             .Build();
         var accessToken = await app.AcquireTokenForClient(new string[] { _config["API:scope"] }).ExecuteAsync();
         Console.WriteLine("token: " + accessToken.AccessToken);
         //var accessToken = await _tokenAcquisition.GetAccessTokenForAppAsync(_TodoListScope);
         _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken);
         _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
     }

I notice that the Audience is still api://[APIclientId] in the token, even if I set the api:scope to api://myapi.iss.it/.default

Is it correct? any idea what could be the problem?








azure-ad-app-registrationdotnet-aspnet-core-webapiazure-ad-app-managementazure-ad-identity-protection
· 6
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi @MaccariFabio, I've checked your code and it seemed fine. And what puzzled me is you said the Audience is correct, that means you can get access token correctly, hence where the 401 error happened? When you calling your api with this access token? If so, I think you need to provide more details on how you verify your access token in your api. Here's my test code and it can return access token with correct role which I defined. I don't have a custom domain, so I changed the default domain api://client_id to api://client_id/tiny


 public async Task<string> gettokenAsync() {
                 IConfidentialClientApplication app;
                 app = ConfidentialClientApplicationBuilder.Create("azure_ad_app_client_id")
                         .WithClientSecret("client_secret_here")
                         .WithAuthority(new Uri("https://login.microsoftonline.com/tenant_name_here"))
                         .Build();
        
                 AuthenticationResult result = null;
                 string[] scopes = new string[] { "api://client_id_which_defined_app_role/tiny/.default" };
                 result = await app.AcquireTokenForClient(scopes)
                         .ExecuteAsync();
                 string accesstoken = result.AccessToken;
                 return accesstoken;
             }


0 Votes 0 ·

Yes, I print the token in the console as you can see from the code above.
I'm not sure the Audience is correct actually, I was expecting it to be api://myapi.iss.it instead of api://[clientId], I'm just not sure about it.

Yes, if I use this token to call a protected API setting the default Application ID URI, it works perfectly.
If a set the App id uri to api://myapi.iss.it, the protected API returns 401, so I guess the Token must have something wrong. May it be the aud?

I mean, the code is pretty standanrd, it comes from the Microsoft template "Daemon application that calls web APIs"

I changed the "daemon app" to a web app where the authentication is configured like this:

             services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
           .AddMicrosoftIdentityWebApp(Configuration)
             .EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["API:scope"] })
             .AddInMemoryTokenCaches();

while the API is:

 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).
 AddMicrosoftIdentityWebApi(Configuration).
 EnableTokenAcquisitionToCallDownstreamApi().
 AddInMemoryTokenCaches();


0 Votes 0 ·

Hi, @MaccariFabio-7507 ,basically, we always need to validate the scopes for delegated permission and roles for app permission when using v2.0 access token, that means when we define a custom api with azure ad, we also need to set a validation in api. The validation works as a filter which will decode the token and check the specific permission, see this section, at the top of this document it also provides a sample project which may be helpful to you : )


1 Vote 1 ·

Would be interesting to know what's the Audience in the token from your example:
is it api://client_id_which_defined_app_role/tiny ?
or just api://client_id_which_defined_app_role ?


0 Votes 0 ·

Hi @MaccariFabio-7507 , thanks for your reply and after decoding my access token, I got the aud with the value of client_id_which_defined_app_role , it's correct as the document said about it.


0 Votes 0 ·
Show more comments

1 Answer

tinywang-msft avatar image
2 Votes"
tinywang-msft answered tinywang-msft commented

Hi, @MaccariFabio-7507 in my opinion, your token is correct and the issue should come from your api validation. According to this section, after enabling bearer token validation in your api program, aud claim will be validated at first. Hence we need to set Audience in appsettings.json. Here I set up a asp.net core mvc program to test it. It worked well and please refer to my code snippet below:

appsettings.json need to add these:

 "AzureAd": {
     "Instance": "https://login.microsoftonline.com/",
     "ClientId": "2c0e06_xxxxxxx_df157",
     "Domain": "tenant_name.onmicrosoft.com", 
     "TenantId": "common",
     "Audience": "client_id_that_expose_api"//you can get it by decoding access token and the aud claim
   }

StartUp.cs need to modify ConfigurationServices and Configure

 public void ConfigureServices(IServiceCollection services)
         {
             services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
             services.AddControllersWithViews();
         }
    
 app.UseRouting();
             // adding next 2 lines in Configure method
             app.UseAuthentication();
             app.UseAuthorization();
    
             app.UseEndpoints(endpoints =>
             {
                 endpoints.MapControllerRoute(
                     name: "default",
                     pattern: "{controller=Home}/{action=Index}/{id?}");
             });

And this is my controller:

 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Identity.Web.Resource;
 using WebApplication1.Models;
    
 namespace WebApplication1.Controllers
 {
     [Authorize]
     public class HelloController : Controller
     {
         public IActionResult Index()
         {
             HttpContext.ValidateAppRole("Tiny.Read");
             Student stu = new Student();
             stu.age = 18;
             return Json(stu) ;
         }
     }
 }


This is my testing result:

115856-image.png



If the answer is helpful, please click "Accept Answer" and upvote it.
Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.
Best Regards,
TinyWang



image.png (39.9 KiB)
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thank you so much, this worked like a charm.
I was actually working on what you said in your previous comment and the documentation you posted about it.
It was really clear that the problem was there indeed (I also saw "Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException" in the API's log).

0 Votes 0 ·

@MaccariFabio-7507 Hi, I'm glad to see it worked, thank for your response : )

0 Votes 0 ·