microsoft-identity-web icon indicating copy to clipboard operation
microsoft-identity-web copied to clipboard

IDW10502: An MsalUiRequiredException

Open michiproep opened this issue 2 years ago • 10 comments

Microsoft.Identity.Web Library

Microsoft.Identity.Web

Microsoft.Identity.Web version

1.25.1

Web app

Sign-in users and call web APIs

Web API

Not Applicable

Token cache serialization

Distributed caches

Description

To me, it seems to be a bug that var res = await _api.CallWebApiForUserAsync(ServiceName, options => { options.RelativePath = "/api/getsomething"; //options.Scope = config.Scope //injected via config }, user: user); throws "IDW10502: An MsalUiRequiredException => inner: An error occured during token acquisition: No account or login hint was passed to the AcquireTokenSilent call" while at the same time this works: var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new string[] { config.Scope }, user: user);

Reproduction steps

  1. Setup AddMicrosoftIdentityWebApp(b2cConfig) .EnableTokenAcquisitionToCallDownstreamApi(new string[] { config.Scope }) .AddDownstreamWebApi("MyApi", builder.Configuration.GetSection("MyApi")) .AddDistributedTokenCaches();//I do use redis
  2. Within Oidc's OnTokenValidated event call -tokenAcquisition.GetAccessTokenForUserAsync //success -_api.CallWebApiForUserAsync both with "user: context.Principal"

Error message

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.35.1.0.MsalUiRequiredException: ErrorCode: user_null Microsoft.Identity.Client.MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.

Id Web logs

fail: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[17] Exception occurred while processing 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.35.1.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.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 authority, 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.DownstreamWebApi.CallWebApiForUserAsync(String serviceName, String authenticationScheme, Action1 calledDownstreamWebApiOptionsOverride, ClaimsPrincipal user, StringContent content) at STPv2.Infrastructure.UrmApi.Me(ClaimsPrincipal user) in C:\Users\mlproe\source\repos\STPv2\STPv2\Infrastructure\UrmApi.cs:line 24 at STPv2.Program.UrmAuthentication(TokenValidatedContext ctx) in C:\Users\mlproe\source\repos\STPv2\STPv2\Program.cs:line 154 at STPv2.Program.<>c.<<ConfigureServices>b__2_7>d.MoveNext() in C:\Users\mlproe\source\repos\STPv2\STPv2\Program.cs:line 103 --- End of stack trace from previous location --- at Microsoft.Identity.Web.MicrosoftIdentityWebAppAuthenticationBuilder.<>c__DisplayClass11_1.<<WebAppCallsWebApiImplementation>b__2>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.RunTokenValidatedEventAsync(OpenIdConnectMessage authorizationResponse, OpenIdConnectMessage tokenEndpointResponse, ClaimsPrincipal user, AuthenticationProperties properties, JwtSecurityToken jwt, String nonce) at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleRemoteAuthenticateAsync() Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler: Error: Exception occurred while processing 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.35.1.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.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 authority, 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.DownstreamWebApi.CallWebApiForUserAsync(String serviceName, String authenticationScheme, Action1 calledDownstreamWebApiOptionsOverride, ClaimsPrincipal user, StringContent content) at STPv2.Infrastructure.UrmApi.Me(ClaimsPrincipal user) in C:\Users\mlproe\source\repos\STPv2\STPv2\Infrastructure\UrmApi.cs:line 24 at STPv2.Program.UrmAuthentication(TokenValidatedContext ctx) in C:\Users\mlproe\source\repos\STPv2\STPv2\Program.cs:line 154 at STPv2.Program.<>c.<<ConfigureServices>b__2_7>d.MoveNext() in C:\Users\mlproe\source\repos\STPv2\STPv2\Program.cs:line 103 --- End of stack trace from previous location --- at Microsoft.Identity.Web.MicrosoftIdentityWebAppAuthenticationBuilder.<>c__DisplayClass11_1.<<WebAppCallsWebApiImplementation>b__2>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.RunTokenValidatedEventAsync(OpenIdConnectMessage authorizationResponse, OpenIdConnectMessage tokenEndpointResponse, ClaimsPrincipal user, AuthenticationProperties properties, JwtSecurityToken jwt, String nonce) at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleRemoteAuthenticateAsync()

Relevant code snippets

var res = await _api.CallWebApiForUserAsync(ServiceName, options =>
{
  options.RelativePath = "/api/getsomething";
  //options.Scope = config.Scope //injected via config
}, user: user);
//AND
var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new string[] { config.Scope }, user: user);

Regression


Expected behavior

_api.CallWebApiForUserAsync get the required access token just as tokenAcquisition.GetAccessTokenForUserAsync does

michiproep avatar Jul 10 '22 18:07 michiproep

@michiproep, do you have a [AuthorizeForScope] attribute on your controller or controller action?

jmprieur avatar Jul 12 '22 00:07 jmprieur

@jmprieur , I'm not calling this from a controller but within OnTokenValidated within Authentication middleware

michiproep avatar Jul 12 '22 06:07 michiproep

I see. That means you didn't request the necessary scopes or claims Look at the Claims member in the exception.

jmprieur avatar Jul 13 '22 02:07 jmprieur

Well, of course I can request all scopes within the initial authorize request but that still does not explain why "ItokenAcquisition" is working while "Idownstreamapi" does not. When I look at the exception, it says "ErrorCode: user_null". But I do provide the same claimPrinciple to both calls.

michiproep avatar Jul 13 '22 05:07 michiproep

Fair question, @michiproep. we'll have a look why ITokenAcquisition works, rather (as if used in the OnTokenValidated event I would not expect it to work either) cc: @jennyf19

jmprieur avatar Jul 13 '22 15:07 jmprieur

Do we have any updates on this please, I'm also having same scenario where I'm trying to get the access token on authentication middleware and getting the same exception, however if we are trying to get token from controller, then it always works but not at the time of OnTokenValidated?

nikhilvats7oct avatar Aug 23 '22 18:08 nikhilvats7oct

In a web app, OnTokenValidated is called when the ID Token is received. The token is not yet obtained by MSAL.NET by redeeming the authentication code, which is why I'm not expecting things to work at that stage.

They could work if you chain the events and make sure that the previous event handler (set by Microsoft identity web) is called before your own code processes the event. This is shown here : https://github.com/AzureAD/microsoft-identity-web/wiki/customization#how-to-query-microsoft-graph-on-token-validated

jmprieur avatar Aug 24 '22 03:08 jmprieur

Thank you for your reply. I tried to chain the events as mentioned in above link but it also doesn't work.

One thing I noticed when app redirects to identity, it is showing the response_type=code, it seems wrong, shouldn't it be "id_token". I'm using AddMicrosoftIdentityWebApp to register my web app and the default response type in this case is "id_token".

I'm not sure if something is missing in my implementation or it is always the case.

https://login.microsoftonline.com/f009f285-5242-433a-9365-daa1edf145c3/oauth2/v2.0/authorize? client_id=xxxx-xxx-xxxx-xxxx &redirect_uri=https%3A%2F%2Flocalhost%3A55878%2Fsignin-oidc &response_type=code &scope=openid%20profile%20offline_access%20api%3A%2F%2Fffe42174-xxxx-xxxx-xxxx-xxxxxxxxe%2Faccess_as_user &code_challenge=qD_Sjpq585bvtLbqog2Cus6IW9r3940wo9s27a1hlXg &code_challenge_method=S256 &response_mode=form_post &nonce=637969021956837698.NzVhZjljZDctOTA1Zi00MmMwLWFhODMtMzEwMzA3ZDI1NGUyZjkxOGVmMGYtZTliZC00MmUyLThhMmEtMmJkZDRmNjE1MjBm &client_info=1 &x-client-brkrver=IDWeb.1.25.1. &state=CfDJ8PpKgloFJPhJla6verKay9QYDQxX4gDLnh4U4fQfcv1TX-Zf-UJm8AfS_zt4qdnZAbA9i7ZfodaIbvJkwTiErrqRD71lvllrP6x_JWPd28rvaIN2UiaLSNTQqaINzmKdRlu2O0faGFiATeZerKyoljtXtXi9PcNYY4osgfmbH9em9I6jq9UVzBvREFnkOOxOpe_iOPjRzBnPLytLSd7KJACZHiXkrIr7bhOlYfDt1MtietU0dK34Rcj4NZ1Dw-7PGAL8QGHFHjmWg6sDoJi3_9EJEP7JkaSWDsp5gcR4p2Hcb3Peh60LZL1x8KvOKCLU04dPUTOr1HpTXkLtqkksCWlwuhpEyCmj3cdIi6Z6I5W6EvnnenwD1aaN4inX6wQv_Q &x-client-SKU=ID_NET6_0 &x-client-ver=6.20.0.0

nikhilvats7oct avatar Aug 24 '22 09:08 nikhilvats7oct

@nikhilvats7oct, many customers don't want Id.Web to use ID tokens (because they read about the implicit flow, and unfortunately, to get an ID token, in the portal you need to check a checkbox which is in an area named "implicit flow" (whereas, indeed, code_idtoken is perfectly ok. Therefore Id.Web only uses code.

If you want to have id_token, you can change the response type in the OpenIdConnect options.

jmprieur avatar Aug 25 '22 03:08 jmprieur

This discussion is going into a complete wrong direction. The initial question was "why CallWebApiForUserAsync is not able to get a token while GetAccessTokenForUserAsync does". It's still not clear why they behave differently.

michiproep avatar Aug 28 '22 04:08 michiproep

I encountered the same issue (ITokenAcquisition.GetAccessTokenForUserAsync works but IDownstreamWebApi.CallWebApiForUserAsync doesn't) on a controller action.

As for the onTokenValidated(), I've found that overriding it with our own code skips the default available in the library where it sets this item in the HttpContext: "JwtSecurityTokenUsedToCallWebAPI". At the time of the token acquisition call, the code checks for this item within the context, and if not found, it returns the error 'no account or login hint was found....'.

So, if you still want to override onTokenValidated(), make sure to include a hack where you explicitly set the 'JwtSecurityTokenUsedToCallWebAPI' key in the HttpContext.Items dictionary. It would look similar to what's being done in the library here: https://github.com/AzureAD/microsoft-identity-web/blob/491b8efb8d8aea7987483a8822aa1628b1d64869/src/Microsoft.Identity.Web/HttpContextExtensions.cs

Do make sure to explicitly remove the item from the dictionary when you are done with the downstream API call (a try-finally block could be used for this)

udaykiran1105 avatar Nov 18 '22 10:11 udaykiran1105

@jmprieur Any further update on this investigation?

udaykiran1105 avatar Nov 18 '22 10:11 udaykiran1105

I encountered the same issue (ITokenAcquisition.GetAccessTokenForUserAsync works but IDownstreamWebApi.CallWebApiForUserAsync doesn't) on a controller action.

As for the onTokenValidated(), I've found that overriding it with our own code skips the default available in the library where it sets this item in the HttpContext: "JwtSecurityTokenUsedToCallWebAPI". At the time of the token acquisition call, the code checks for this item within the context, and if not found, it returns the error 'no account or login hint was found....'.

So, if you still want to override onTokenValidated(), make sure to include a hack where you explicitly set the 'JwtSecurityTokenUsedToCallWebAPI' key in the HttpContext.Items dictionary. It would look similar to what's being done in the library here: https://github.com/AzureAD/microsoft-identity-web/blob/491b8efb8d8aea7987483a8822aa1628b1d64869/src/Microsoft.Identity.Web/HttpContextExtensions.cs

Do make sure to explicitly remove the item from the dictionary when you are done with the downstream API call (a try-finally block could be used for this)

For this, I call the tokenAquisition with context.Principal where context comes from onTokenValidated = async context =>{...}

michiproep avatar Jan 11 '23 10:01 michiproep

Any further update on this issue?

dmamagiannos avatar Mar 24 '23 12:03 dmamagiannos

I haven't checked on this issue in quite a while but with the latest version (2.9.0) neither one is working now. Additional info: -I do request the scope in the initial .EnableTokenAcquisitionToCallDownstreamApi(new[] { MyScope }) -I can see the accessToken in TokenEndpointResponse within the TokenValidatedContext which is actually new in v2.x => So, I could actually pass this token along but then I cannot make use of ITokenAquisition nor IDownstreamApi

To me, it seems like the tokenCache is not initialzed after redeemAuthCode internally anymore but this is just a guess

michiproep avatar Apr 23 '23 05:04 michiproep

This problems still remains on version 2.11.0 of Microsoft.Identity.Web NuGet package. I am obliged to force log out to solve the problem, but this leds to complains from my customers. Is there any update on this issue?

On a NET 7.0 blazor web server, I have the below configuration :

builder.Services .AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAdB2C") .EnableTokenAcquisitionToCallDownstreamApi(builder.Configuration.GetSection("AzureADB2CScopesSettings").Get<AzureADB2CScopesSettings>().Scopes) .AddSessionTokenCaches();

I have checked both "Access tokens (used for implicit flows)" and "ID tokens (used for implicit and hybrid flows)" on blazor's application.

The user flow configuration is :

  • Refresh token sliding window lifetime : No expiry
  • Web app session timeout : Rolling
  • Single sign-on configuration : Disabled (I came to conclusion that this is the best options. Less exceptions thrown)
  • Enable keep me signed in session : Yes

May is something that I have misconfigured ? Thank you

dmamagiannos avatar Jun 05 '23 18:06 dmamagiannos