microsoft-identity-web
microsoft-identity-web copied to clipboard
IDW10502: An MsalUiRequiredException
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
- Setup AddMicrosoftIdentityWebApp(b2cConfig) .EnableTokenAcquisitionToCallDownstreamApi(new string[] { config.Scope }) .AddDownstreamWebApi("MyApi", builder.Configuration.GetSection("MyApi")) .AddDistributedTokenCaches();//I do use redis
- 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(IEnumerable
1 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, Action
1 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(IEnumerable
1 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, Action
1 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, do you have a [AuthorizeForScope]
attribute on your controller or controller action?
@jmprieur , I'm not calling this from a controller but within OnTokenValidated within Authentication middleware
I see. That means you didn't request the necessary scopes or claims Look at the Claims member in the exception.
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.
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
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?
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
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, 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.
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.
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)
@jmprieur Any further update on this investigation?
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 =>{...}
Any further update on this issue?
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
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