AzureAD / microsoft-identity-web

Helps creating protected web apps and web APIs with Microsoft identity platform and Azure AD B2C
MIT License
682 stars 214 forks source link

Calling ITokenAcquisition.GetAccessTokenForUserAsync from class that implements Microsoft.AspNetCore.Authentication.IClaimsTransformation throws an MsalUiRequiredException #2195

Open HomerBart opened 1 year ago

HomerBart commented 1 year ago

Microsoft.Identity.Web Library

Microsoft.Identity.Web

Microsoft.Identity.Web version

2.7.0

Web app

Sign-in users and call web APIs

Web API

Not Applicable

Token cache serialization

In-memory caches

Description

I've created a class that uses ITokenAcquisition.GetAccessTokenForUserAsync to get an access token from AAD for calling a downstream service. When calling this code from a controller/action it works as expected and returns the access token without any user interaction, however when calling the same code from a class that implements Microsoft.AspNetCore.Authentication.IClaimsTransformation for adding claims to the incoming principal (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims?view=aspnetcore-7.0#extend-or-add-custom-claims-using-iclaimstransformation) the call to ITokenAcquisition.GetAccessTokenForUserAsync throws a Microsoft.Identity.Web.MicrosoftIdentityWebChallengeUserException

Reproduction steps

Setup

Reproduction steps I've created a sample application that demonstrates the issue. It can downloaded from https://github.com/HomerBart/SampleApp

To run the sample:

  1. Update the appsettings.json file and replace the TODO values with appropriate values
  2. Ensure there are breakpoints set on the following lines (these are key bits of code):
    • MyDownstreamService.cs, lines 19 and 20
    • MyClaimsTransformation.cs lines 23 and 24
    • HomeController.cs lines 22 and 23
  3. Run the application (in an incognito window). Sign-in and consent to any requested permissions. The application should work and an access token will return in the HomeController.
  4. Stop the application
  5. Uncomment line 19 in Program.cs. This will add MyClaimsTransformation to the request pipeline for adding claims to the incoming principal
  6. Run the application again (in an new incognito window). Sign-in and consent to any requested permissions. The breakpoints in the MyClaimsTransformation should be hit, but the access token won't be returned because an MsalUiRequiredException will occur

Error message

Microsoft.Identity.Web.MicrosoftIdentityWebChallengeUserException: IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent. ---> MSAL.NetCore.4.51.0.0.MsalUiRequiredException: ErrorCode: user_null Microsoft.Identity.Client.MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call. at Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.ApiConfig.Executors.ClientApplicationBaseExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenSilentParameters silentParameters, CancellationToken cancellationToken) at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForWebAppWithAccountFromCacheAsync(IConfidentialClientApplication application, ClaimsPrincipal claimsPrincipal, IEnumerable1 scopes, String tenantId, MergedOptions mergedOptions, String userFlow, TokenAcquisitionOptions tokenAcquisitionOptions) at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions) StatusCode: 0 ResponseBody:
Headers: --- End of inner exception stack trace --- at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions) at Microsoft.Identity.Web.TokenAcquisition.GetAccessTokenForUserAsync(IEnumerable1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions) at External.Authman.AuthorisationRepository.CreateAuthorisationClientAsync() in C:\Development\git\RamsysInterfaceNet7\External\Authman\AuthorisationRepository.cs:line 65

Id Web logs

No response

Relevant code snippets

As described above.

Regression

No response

Expected behavior

The call to ITokenAcquisition.GetAccessTokenForUserAsync shouldn't throw the exception when called from the class that implements Microsoft.AspNetCore.Authentication.IClaimsTransformation for adding claims to the incoming principal. It should work just like it does when being called from a controller.

HomerBart commented 1 year ago

Microsoft, Is it possible to confirm if this is bug? I'm currently blocked and unable to make any progress. Thank you.

jmprieur commented 1 year ago

@tratcher: when are the IClaimsTransformation called? would they be called before the auth code is redeemed?

Tratcher commented 1 year ago

IClaimsTransform is called after the authentication handler finishes. Code redemption is part of the auth handler.

https://github.com/dotnet/aspnetcore/blob/442d656e920d6122c16d703c56df3b4aa0a4ee22/src/Http/Authentication.Core/src/AuthenticationService.cs#L78-L97

HomerBart commented 1 year ago

Hi @jmprieur. Any update on issue? Thanks.

HomerBart commented 1 year ago

Hi @jmprieur. Any update on this issue? I'm blocked and if there isn't a way forward then we need to consider abandoning Microsoft.Identity.Web. Thank you.

jmprieur commented 1 year ago

@HomerBart : I'll try to find the time to look at this issue this week-end.

ma-lar commented 1 year ago

@jmprieur Did you have time to look at this issue? We are stuck with the same issue.

@HomerBart Did you find a workaround?

HomerBart commented 1 year ago

Hi @ma-lar, Yes, I found if you explicitly pass the ClaimsPrincipal to GetAccessTokenForUserAsync it works:

public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
    if (principal.Identity != null && principal.Identity.IsAuthenticated && principal.Identity.Name != null)
    {
        ...
        string accessToken = await _tokenAcquisitionService.GetAccessTokenForUserAsync(_authmanServiceOptions.Scopes, user:principal);
        ...
    }
    return principal;
}

Ideally this shouldn't be required, so I still think this is a bug in Microsoft.Identity.Web

vikasdinavahi commented 1 year ago

I followed the thread and did try @HomerBart. It did not work. image