3

We have a number of ASPNET Core Web Apis in Azure that we call on behalf of a User. That user has normally signed into an ASPNET Web Site, also in Azure.

We are introducing an Audit Service. That feels like it should be called on behalf of the calling service rather that the authenticated user.

  • The Audit Service has an associated App Registration in Azure AD
  • The Audit Service has a scope called "access_as_application" although having seen documentation about a ".default" scope I wasn't sure that i needed a scope
  • The calling application (ASPNET Core Web Site) has been added in the "Authorized client applications" section against the previously mentioned scope

In the calling application I am getting an access token for the app rather than the user by using GetAccessTokenForAppAsync.

var accessToken = await this.tokenAcquisition.GetAccessTokenForAppAsync(this.auditApiScope);
System.Diagnostics.Debug.WriteLine($"access token-{accessToken}");
this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

Currently I am running the calling application and the audit service on my local development machine.

When I make the call to the audit service I am getting a 401 Unauthorized

var response = await this.httpClient.PostAsync($"{this.auditApiBaseAddress}v1/entries", requestContent);

UPDATE

I have added the Azure Ad App Id of the calling application as a knownClientApplication on the Audit Service, via the App Manifest. That did not prevent the 401

"knownClientApplications": [
        "7ac7f49d-e9fa-4e1b-95b2-03e0e1981f58"
    ],

UPDATE 2

I can see that the instance of the service running in Visual Studio is reporting a stack trace. It is referring to a IDW10201 issue.

System.UnauthorizedAccessException: IDW10201: Neither scope or roles claim was found in the bearer token. 
   at Microsoft.Identity.Web.MicrosoftIdentityWebApiAuthenticationBuilderExtensions.<>c__DisplayClass3_1.<<AddMicrosoftIdentityWebApiImplementation>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.Identity.Web.MicrosoftIdentityWebApiAuthenticationBuilder.<>c__DisplayClass14_0.<<CallsWebApiImplementation>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Any thoughts why?

Pat Long - Munkii Yebee
  • 3,592
  • 2
  • 34
  • 68
  • Have you added the calling application's id in trusted client applications in the audit service application manifest? This is something we had to do manually. – Gaurav Mantri Jun 01 '21 at 10:31
  • Is that in the preAuthorizedApplications node? if so then yes that was added, must have been as part of the adding to "Authorized client applications" – Pat Long - Munkii Yebee Jun 01 '21 at 11:02
  • "knownClientApplications" node in application manifest. – Gaurav Mantri Jun 01 '21 at 11:04
  • Did you sign out and signed back in? I had similar issue and adding it calling application's id in "knownClientApplications" fixed it for me. I had to recreate everything though. You can read more about my issue here - https://github.com/AzureAD/microsoft-identity-web/issues/1217. – Gaurav Mantri Jun 01 '21 at 11:13
  • Are you currently performing server-to-server interaction(https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow)? I think if you set the `scope` to: `api://{api app client id}/.default`, it should solve the problem. try it. – Carl Zhao Jun 02 '21 at 06:30

2 Answers2

4

You should currently be performing server-to-server interaction, that is, no user involvement. So your server application needs to create an appRole, and then grant the app Role as an application permission to the client application.

First, you need to expose the api of the server application protected by Azure, which can be configured according to the following process:

Azure portal>App registrations>Expose an API>Add a scope>Add a client application

enter image description here

Then you need to create the appRole of the server application, and then grant that role as an application permission to the client application.

enter image description here

Next, go to client application>API permissions>Add a permission>My APIs>your api application.

enter image description here

Finally, you need to obtain an access token using the client credential flow where no user is logged in:

enter image description here

Parse the token:

enter image description here

Carl Zhao
  • 8,543
  • 2
  • 11
  • 19
  • I'm trying this out now. I notice that you app id URL is guid based whereas mine is https://ourdomain.co.uk/us.app-name. Is that going to be an issue? Did you add an application scope or did you simply "Add a client application"? – Pat Long - Munkii Yebee Jun 02 '21 at 07:54
  • @PatLong-MunkiiYebee This should not be a issue, the app id URL can be customized. – Carl Zhao Jun 02 '21 at 08:27
  • @PatLong-MunkiiYebee You can configure according to my answer, I added both. – Carl Zhao Jun 02 '21 at 08:30
  • My custom application (Audit-Service) does appeart in the list of My APIs that is shown at "client application>API permissions>Add a permission>My APIs" This is obviously key but I cannot see why it is not showing – Pat Long - Munkii Yebee Jun 02 '21 at 13:25
  • OK, so the Audit Service did not appear in the list but if I typed the AppId into the search box "APIs my organization uses" it does appear and I can choose the appRole. Now checking token – Pat Long - Munkii Yebee Jun 02 '21 at 13:32
  • I'm struggling on making this to work till i found your answer, thanks @CarlZhao – Vincent Dagpin Jan 21 '23 at 20:57
0

Whilst I've marked Carl Zhao's contribution as the answer I found the screenshots a bit hard to follow so this is my attempt at making that a bit clearer.

In this scenario where we want authentication between Azure Ad registered application (client) and another Azure Ad registered application (Audit Service) scopes were not the solution. Rather than exposing a scope we needed to expose an appRole.

The steps required to expose and then request access to the app role were

  1. App Registrations -> Audit Service -> Manage -> App roles -> Create app role
  2. When creating the app role ensure the Allowed member type is "Applications"
  3. Now go to App Registrations -> YourClientApplication -> Api permissions -> Add a permission
  4. I expected the Audit Service to appear under "My APIs" in the "Request API permissions panel". I did not, the only way I could request permisison to the previously created AppRole was to enter the AppId of the Audit Service in the search box under "APIs my organization uses"
  5. Once I was able to select the audit service I selected "Application permissions" rather than "Delegated permissions" and then I selected the specific role

Once the client application had been granted access we needed to write code get to an access token. Using Mictosoft.Identity.Web library

var accessToken = await this.tokenAcquisition.GetAccessTokenForAppAsync(this.auditApiScope);
System.Diagnostics.Debug.WriteLine($"access token-{accessToken}");
this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

Note the call to GetAccessTokenForAppAsync not GetAccessTokenForUserAsync. GetAccessTokenForAppAsync still requires a scope however as already stated a custom scope is not needed. The scope is ".default" so the string passed to that call in our case was https://ourdomain/audit-service/.default" which is our App ID URI plus ".default"

Pat Long - Munkii Yebee
  • 3,592
  • 2
  • 34
  • 68