Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Azure AD Authentication 401 error "the audience is invalid" AddAzureADBearer .Net Core Web Api

I'm trying to create a simple example of Azure AD authentication using this sample except for my client is JQuery. I am not sure why I get the 401 error about the audience is invalid when the token shows the audience is https://myportal.onmicrosoft.com/test_core_web_api_spa. This matches the API definition in Azure. The only missing piece is the custom scope of user_impersonation but when I make the call using MSAL clientApplication.acquireTokenSilent(tokenRequest2) to acquire the token it my scopes matches the full URL of the API with scope:

const tokenRequest2 = {
    scopes: ["https://myportal.onmicrosoft.com/test_core_web_api_spa/user_impersonation"]
};

In the API to establish authentication I am using this code (I noticed not many examples use this method)

services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
   .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));

And the configuration for the API is

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "myportal.onmicrosoft.com",
    "TenantId": "my-tenant-guid",
    "ClientId": "my-api-client-guid"
  },

I've noticed many examples showing a different format for the API (I assume these are older version) but the exposed API scope is listed in Azure as https://myportal.onmicrosoft.com/test_core_web_api_spa/user_impersonation. I have also added the guid of the client using the Azure dashboard to access this exposed API scope.

Any ideas where I have gone wrong? Alternately, any simple examples using MSAL, JQuery for the client, and a simple .Net Core Web Api? Seems like all the examples I find are out of date or use a different client or a different authentication method.

Update to show expose api settings in Azure for web api app. I've added an image from Azure showing the settings for the "expose an api" screen. I've added the custom scope user_impersonation then added the client and granted it access to that scope. As you can see my Azure subscription does not have the api://guid format that is seen by others. When I try to use that api://guid format I get the error The resource principal named api://guid was not found in the tenant.

I also added image of the token. The aud tag matches my web api app name in Azure. And the scp lists the scope that I attached to my scopes request. I just cannot see what else to try.

enter image description here

expose api settings for web api

like image 756
pretzelb Avatar asked Nov 15 '19 00:11

pretzelb


3 Answers

The problem was the configuration data for the Web API. When they say the ClientId what they really want is the value under the "expose an API" option where it says "Application ID URI". What I was putting in there was the guid for the Web Api application registration. Below is how it should look.

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "myportal.onmicrosoft.com",
    "TenantId": "mytenant-guid",
    "ClientId": "https://myportal.onmicrosoft.com/test_core_web_api_spa"
  },

Note on the API format. It appears that when you register the application directly in Azure the format for the exposed API will be api://app-guid. But if you first create your application using Visual Studio then the format will default to something like https:///myportal.onmicrosoft.com/project-name-in-visual-studio.

like image 198
pretzelb Avatar answered Oct 27 '22 12:10

pretzelb


Like most people here I was stuck on this for a little while and the documentation seriously lets it down.

While following the MS guide it populates appsettings file in the Server project like so

"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "contoso.onmicrosoft.com",
"TenantId": "e86c78e2-8bb4-4c41-aefd-918e0565a45e",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
 }

Frustratingly this fix is as simple as pre-fixing the client ID with api:// so that it matches both the audience in the JWT and the Application ID URI on the Expose an API section of your server app in AAD

"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "contoso.onmicrosoft.com",
"TenantId": "e86c78e2-8bb4-4c41-aefd-918e0565a45e",
"ClientId": "api://41451fa7-82d9-4673-8fa5-69eff5a761fd",
} 

enter image description here

like image 3
Jon Edwards Avatar answered Oct 27 '22 13:10

Jon Edwards


Don't just look at the below code, PLEASE READ!

Building on what pretzelb said, don't worry so much about how your client is configured (as long as your client is working). Assuming the error you are getting is related to invalid issuer, or invalid audience then try the following.

Debug your api and set a debug point somewhere after your client has tried to connect and look at HttpContext - Request - Headers - Values, in there you will see your token so drop that in jwt io website and you should find your issuer and your audience which may be completely different than you expected. At this point change your TokenValidationParameters to agree with what you found in the token, then it should work.

Using .Net Core 3.1 and Micorosft.Identity.Web (currently in preview), I'll show you what worked for me:

Here is my config with fake guids. Do not try to use the same instance, you need to look at the issuer in the token your client is sending.

"AzureAD": {
    "Instance": "https://sts.windows.net/",
    "ClientId": "28e36e14-5191-4987-bcdf-982d958de2b3",
    "Domain": "funco.com",
    "TenantId": "744ce43f-a10f-499f-29f3-7je6ef439787"
  }

create a model for the AzureAd config section somewhere:

public class AzureModel
    {
        public String Instance { get; set; }
        public String ClientId { get; set; }
        public String Domain { get; set; }
        public String TenantId { get; set; }
    }
}

then...

services.AddProtectedWebApi(Configuration, AzureADDefaults.AuthenticationScheme,  AzureADDefaults.JwtBearerAuthenticationScheme);
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
    var azureSettings = Configuration.GetSection(AzureADDefaults.AuthenticationScheme).Get<AzureModel>();
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        ValidateIssuer = true,
        ValidIssuer = $"{azureSettings.Instance}{azureSettings.TenantId}/",
        ValidateAudience = true,
        ValidAudience = $"https://{azureSettings.Domain}/{azureSettings.ClientId}",
        //ValidateLifetime = true,
        //ClockSkew = TimeSpan.Zero
    };

    options.Events = new JwtBearerEvents();

    options.Events.OnTokenValidated = async context =>
    {
            await Task.FromResult(0);
    };
    options.Events.OnAuthenticationFailed = async context =>
    {
        await Task.CompletedTask;
    };
    options.Events.OnMessageReceived = async context =>
    {
        await Task.CompletedTask;
    };
});
like image 2
Post Impatica Avatar answered Oct 27 '22 13:10

Post Impatica