Closed Pruzzo closed 2 years ago
@Pruzzo do you get this exception on the debugger and the app re-signs-in the user? or does it just crash?
I get the error in the debugger, if i logout and login again it works fine but the exception is just thrown, nothing else
But when you continue the execution on the debugger, you are automatically re-signed-in? aren't you?
No i am not re-signed in automatically :)
you mean the application crashes? you don't see the sign-in dialog?
No, when this error comes i do not see the signin dialog
I mean when you continue. I'm trying to understand if the app crashes?
The fact that there is an exception is expected/normal,. But it should be filtered by [AuthorizeForScopes]
I'm assuming you have [AuthorizeForScopes(new string[] { "user.read", "onlineMeetings.readWrite" }]
?
Yes, that is what i have.
[AuthorizeForScopes(Scopes = new string[] { "user.read", "onlineMeetings.readWrite" })]
I've also followed the idea of @TiagoBrenck here and i've seen that the attribute is an exception filter but the exception thrown is a lever lower, so i copied the code and i wrote mine to catch the MsalUiRequiredException
` MsalUiRequiredException msalUiRequiredException = context.Exception as MsalUiRequiredException; if (msalUiRequiredException == null) { msalUiRequiredException = context.Exception?.InnerException as MsalUiRequiredException; if (msalUiRequiredException == null) msalUiRequiredException = context.Exception?.InnerException?.InnerException as MsalUiRequiredException;
}`
this works and msalUiRequiredException is not null but i did not manage to make the filter work properly (an exception is thrown on properties.SetParameter<ICollection<string>>
[Object cannot be null ] )
`public class MsalUIExceptionFilter : ExceptionFilterAttribute
{
///
/// <summary>
/// Key section on the configuration file that holds the scope value
/// </summary>
public string ScopeKeySection { get; set; }
/// <summary>
/// Handles the MsaUiRequiredExeception
/// </summary>
/// <param name="context">Context provided by ASP.NET Core</param>
public override void OnException(ExceptionContext context)
{
MsalUiRequiredException msalUiRequiredException = context.Exception as MsalUiRequiredException;
if (msalUiRequiredException == null)
{
msalUiRequiredException = context.Exception?.InnerException as MsalUiRequiredException;
if (msalUiRequiredException == null)
msalUiRequiredException = context.Exception?.InnerException?.InnerException as MsalUiRequiredException;
}
if (msalUiRequiredException != null)
{
if (CanBeSolvedByReSignInUser(msalUiRequiredException))
{
// the users cannot provide both scopes and ScopeKeySection at the same time
if (!string.IsNullOrWhiteSpace(ScopeKeySection) && Scopes != null && Scopes.Length > 0)
{
throw new InvalidOperationException($"Either provide the '{nameof(ScopeKeySection)}' or the '{nameof(Scopes)}' to the 'AuthorizeForScopes'.");
}
// If the user wishes us to pick the Scopes from a particular config setting.
if (!string.IsNullOrWhiteSpace(ScopeKeySection))
{
// Load the injected IConfiguration
IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
if (configuration == null)
{
throw new InvalidOperationException($"The {nameof(ScopeKeySection)} is provided but the IConfiguration instance is not present in the services collection");
}
Scopes = new string[] { "onlineMeetings.readWrite" };
}
var properties = BuildAuthenticationPropertiesForIncrementalConsent(Scopes, msalUiRequiredException, context.HttpContext as HttpContext);
context.Result = new ChallengeResult(properties);
}
}
base.OnException(context);
}
private bool CanBeSolvedByReSignInUser(MsalException ex)
{
// ex.ErrorCode != MsalUiRequiredException.UserNullError indicates a cache problem.
// When calling an [Authenticate]-decorated controller we expect an authenticated
// user and therefore its account should be in the cache. However in the case of an
// InMemoryCache, the cache could be empty if the server was restarted. This is why
// the null_user exception is thrown.
return (ex.ErrorCode.Contains(MsalError.UserNullError) || ex.ErrorCode.Contains(MsalError.InvalidGrantError));
}
/// <summary>
/// Build Authentication properties needed for an incremental consent.
/// </summary>
/// <param name="scopes">Scopes to request</param>
/// <param name="ex">MsalUiRequiredException instance</param>
/// <param name="context">current http context in the pipeline</param>
/// <returns>AuthenticationProperties</returns>
private AuthenticationProperties BuildAuthenticationPropertiesForIncrementalConsent(
string[] scopes, MsalUiRequiredException ex, HttpContext context)
{
var properties = new AuthenticationProperties();
// Set the scopes, including the scopes that ADAL.NET / MASL.NET need for the Token cache
//string[] additionalBuildInScopes = {OidcConstants.StandardScopes.OpenId, OidcConstants.StandardScopes.OfflineAccess, OidcConstants.StandardScopes.Profile};
//properties.SetParameter<ICollection<string>>(OpenIdConnectParameterNames.Scope,scopes.Union(additionalBuildInScopes).ToList());
// Attempts to set the login_hint to avoid the logged-in user to be presented with an account selection dialog
var loginHint = context.User.GetLoginHint();
if (!string.IsNullOrWhiteSpace(loginHint))
{
properties.SetParameter(OpenIdConnectParameterNames.LoginHint, loginHint);
var domainHint = context.User.GetDomainHint();
properties.SetParameter(OpenIdConnectParameterNames.DomainHint, domainHint);
}
// Additional claims required (for instance MFA)
if (!string.IsNullOrEmpty(ex.Claims))
{
//properties.Items.Add(OidcConstants. AdditionalClaims, ex.Claims);
}
return properties;
}
}`
Hello @Pruzzo I'm so sorry to start with the issue few months later. I'm on it now Can you please kindly update if you still need help?
Hi @aremo-ms, it was necessary to me to carry out with the job and so i found a walkaround, setting a control that simpy tries to use the token and if it gives an exception the user is logged out forcing the login again.
Hi @aremo-ms, it was necessary to me to carry out with the job and so i found a walkaround, setting a control that simpy tries to use the token and if it gives an exception the user is logged out forcing the login again.
@Pruzzo If you had to find workaround, then something is wrong or you have a special case. I don't remember any issue in our samples. Do you think that the sample is incorrect?
I was not reffering at the sample, my case is that i was just expection the token to auto refresh using the AcquireTokenSilent but it seems to not be working. Honestly i do not remember precisely all the steps i've followed. In principle i remeber that i was not able to acquire silently the token in order to auto refresh it when expires.
@Pruzzo So you've tried to make something like this and it didn't work?
Closing this issue after 14 days without response
Hi, ive been having the same issue. I am currently building a .net core mvc application and I keep getting the "no account or login hint was passed to the acquiretokensilent call" error. I have tried it with the same code as provide here as well as using the GraphServiceClient provided by the controller. It works on initial loading of the page, but once the page is reloaded the error reappears. It looks asif the cache gets cleared, but i have no way of confirming this
first code section : try { var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "User.Read", "User.Read.All", "User.ReadBasic.All", "Tasks.Read", "Tasks.Read.Shared", "Tasks.ReadWrite", "Tasks.ReadWrite.Shared", "Group.ReadWrite.All" });
GraphServiceClient graphServiceClient = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(request => { request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token); return Task.CompletedTask; }));
User user = await graphServiceClient.Me.Request().GetAsync(); } catch { }
second code section: User user = await _graphServiceClient.Me.Request().GetAsync();
and the error -> MsalUiRequiredException : No account or login hint was passed to the AcquireTokenSilent call.
If it could help you i found this walkaround.
try { var taRes = await tokenAcquisition.GetAuthenticationResultForUserAsync(new List<string> { "user.read", "onlinemeetings.readwrite" }, "common", null, User, new TokenAcquisitionOptions() { ForceRefresh = true }).ConfigureAwait(false); } catch { foreach (var cookie in Request.Cookies) { Response.Cookies.Delete(cookie.Key); } return BadRequest(new ErrorResult("session_expired")); }
This logs out the user forcing to login again
I have the same issue...
I set up graph client in the startup class like below. I thought adding AddSessionTokenCaches()
would allow the tokens to persist in a user session even if the server restarts. However, it only works whilst the server is online for the first time - if I restart the server then the access tokens no longer work. I have to clear my cookies to force the app to reauthenticate and get a new access token which then works.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(Configuration.GetSection("GraphApi"))
.AddSessionTokenCaches();
}
I have the below code in a Graph API service method like so...
DriveItem = await graphserviceClient
.Sites[{siteId}]
.Drive
.Items[{itemId}]
.Children
.Request()
.AddResponseAsync(driveItem);
@Pruzzo So you've tried to make something like this and it didn't work?
Is there a way to use the example you have said, in my graph API service, to make sure a new access token is passed in each time I call the graph api on behalf of a user?
Hello @crobbo You can refer to this page for detailed explanations. The session token cache you've chose to use is for a memory cache, The memory exists only while server is on. If you need to persist the token, then use SQL server option. In case you'd like to get a fresh token every time you call Graph, then you should consider calling AcquireTokenSilent method. See example here for public and confidential applications
Thanks, I'll take a look at the example. My workaround has been to add the AuthorizeForScopes
attribute to my controller and this seems to work (I think it calls AquireTokenSilent
in the background which is needed as our tokens were setup to expire after 15 or 30mins)
Its weird as in a development environment the AuthorizeForScopes
throws an exception and causes the debugger to pause the code. Whereas once the app is deployed it works fine. Not really sure what's going on but it doesn't feel like this should be throwing any exceptions.
I'll have a read of the example to see if I can maybe refactor my code to call the AquireTokenSilent
manually, rather than relying on the AuthorizeForScopes
attribute.
Thanks, I'll take a look at the example. My workaround has been to add the
AuthorizeForScopes
attribute to my controller and this seems to work (I think it callsAquireTokenSilent
in the background which is needed as our tokens were setup to expire after 15 or 30mins)
This is not a workaround, @crobbo. There can be exceptions anyway in some scenarios, so it's better for the attribute (which is an ExceptionFilter) to process them. This is the way to do. See https://github.com/AzureAD/microsoft-identity-web/wiki/Managing-incremental-consent-and-conditional-access
Does [AuthorizeForScopes(Scopes = new[] { "{scopes}" })] not process and handle MsalUiRequiredException
?
As mentioned, since I added AthourizeForScopes to my controller it works when deployed, the app does not crash due to the exception, I get a new user token and the user is able to access SharePoint via the app as intended. The exception only forces the code to stop in development locally and once I continue through the exception the app works as intended...
I followed your guide adding the extra code to the startup class and Micrsoft.Web.Indentity.UI. I also manually called tokenAquisition.GetAccessTokenForUserAsync({scopes})
and when I do this I get exactly the same exception, `MsalUiRequiredException - no account or login was passed to AquireTokenSilent.
Ihink I'll try the clearing cookie method next...
@crobbo : yes it does. That's what exception filters do.
So, is there any solution to this problem? or any workaround at least?? I too am stuck here...
I am also stuck, I've spent so many hours trying to find a workaround. The workaround listed here doesn't work for me.
Closing this issue after 14 days without response
So you take several months to respond to the issue, then close the issue just 2 weeks of not getting a response when there is clearly an issue
I'm going through this now and still not seeing a straightforward solution
Have you all tried adding the AuthorizeForScopes to the controller?
It worked for me but as I wrote above it raises an exception, not ideal, but the app will still work in production. In local development environment just get Visual studio to ignore to the exception too.
I am yes, I'm using....
[Authorize(AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)] [AuthorizeForScopes(Scopes = new string[] { "user.read" })]
The issue is that it works fine if the user doesn't have a token, it forces the login. However if the user has a token but it's expired it does not force the login and the code in the method continues. When I then try to get graph user information, I get the error posted in this issue.
So I'm in a state where it won't force the user to re-login and I can't get the user information.
I am yes, I'm using....
[Authorize(AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)] [AuthorizeForScopes(Scopes = new string[] { "user.read" })]
The issue is that it works fine if the user doesn't have a token, it forces the login. However if the user has a token but it's expired it does not force the login and the code in the method continues. When I then try to get graph user information, I get the error posted in this issue.
So I'm in a state where it won't force the user to re-login and I can't get the user information.
In addition to this, this solution only works when you hit the controller, I want to refresh the token in Blazor UI before calling web API
@StevTechy: doesn't this work in your blazor page? https://github.com/AzureAD/microsoft-identity-web/wiki/Managing-incremental-consent-and-conditional-access#in-the-blazor-page-itself
I suggest you re-open this @aremo-ms. We see this issue occurring in our Blazor Server app regularly. The issue goes away if we clear the browser cookies, but persists until we do. This is not a viable solution for large-scale applications with thousands of users.
I have set up everything as per the documentation as far as I can tell. AAD redirect works fine for the most part, but at some point - I think when a cached token expires and / or does not match the cookie, the call to MicrosoftIdentityConsentAndConditionalAccessHandler.HandleException(ex)
stops performing the AAD redirect. The code in question is as follows:
try
{
// Always call GetAccessTokenForUserAsync so a new token is provided if the old token has expired.
// Note that this call can fail / halt silently without raising an exception
// and code will stop executing. This only happens if this method is called synchronously.
// See the below link for more info:
// https://github.com/AzureAD/microsoft-identity-web/issues/1801#issuecomment-1182986661
var token = await _tokenAcquisition
.GetAccessTokenForUserAsync(new[]
{
_options.DownstreamAPIAccessTokenScope
});
_tokenProvider.AccessToken = token;
_tokenProvider.Scheme = JwtBearerDefaults.AuthenticationScheme;
} catch(MicrosoftIdentityWebChallengeUserException ex)
{
// The below line will redirect the user to AAD (for consent and / or to get a new / existing access token)
// if they do not have an active access token for the API with the specified scope.
_consentHandler.HandleException(ex);
return null;
}
We are using SQL Server caching, and need to handle everything on the blazor-server side; we cannot apply attributes to the downstream API, because the same software is used with different authorisation mechanisms depending on the deployment. The auth-related setup is as follows:
public static IServiceCollection AddClientAzureADAuth(this IServiceCollection services, IConfiguration config)
{
// AAD specific services
services.AddScoped<IClientIdentityService, AzureADIdentityService>();
services.AddScoped<IAPITokenService, AzureAADTokenService>();
var initialScopes = config
.GetValue<string>(ClientAuthConstants.DownstreamAPIScopesConfigKeyName)?
.Split(' ');
services
.Configure<AzureAADBlazorOptions>(config.GetSection(ClientConfigurationConstants.AzureADSectionName));
services
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(config.GetSection(ClientAuthConstants.AzureADConfigSectionName))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddDownstreamWebApi(ClientAuthConstants.APIName, config.GetSection(ClientConfigurationConstants.WebAPISectionName))
// AddInMemoryTokenCaches() is good for testing ang development, but a distributed
// token cache is more suitable for production
// https://docs.microsoft.com/en-gb/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=aspnet
// .AddInMemoryTokenCaches();
.AddDistributedTokenCaches();
// Add distributed cache options
services.AddDistributedSqlServerCache(options =>
{
var cacheConnectionString = config.GetValue<string>(ClientAuthConstants.AzureADTokenCacheConnectionStringKey);
options.ConnectionString = cacheConnectionString;
options.SchemaName = "dbo";
options.TableName = "TokenCache";
// You don't want the SQL token cache to be purged before the access token has expired. Usually
// access tokens expire after 1 hour (but this can be changed by token lifetime policies), whereas
// the default sliding expiration for the distributed SQL database is 20 mins.
// Use a value which is above 60 mins (or the lifetime of a token in case of longer lived tokens)
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
services
.AddControllersWithViews()
.AddMicrosoftIdentityUI();
// Custom Auth handler
services.AddScoped<IAuthorizationHandler, AzureADUserRequirementAuthorizationHandler>();
services
.AddAuthorization(options =>
{
options.AddPolicy(PolicyNames.UserPolicy,
policy =>
{
var aadOptions = config
.GetSection(ClientAuthConstants.AzureADConfigSectionName)
.Get<AzureAADBlazorOptions>();
policy.RequireAuthenticatedUser(); // Adds DenyAnonymousAuthorizationRequirement
// Some claims are required by the app
policy.RequireClaim(ClaimConstants.PreferredUserName);
policy.RequireClaim(ClaimConstants.ObjectId);
policy.RequireClaim(ClaimConstants.TenantId, aadOptions.TenantId);
if(aadOptions.RequiresAppRole)
{
policy.RequireClaim(ClaimConstants.Role, aadOptions.RequiredRole);
}
// User must be a user
policy.AddRequirements(new AzureADUserRequirement());
});
// Set the default auth policy so that the entire app uses the same one
var UserPolicy = options.GetPolicy(PolicyNames.UserPolicy);
options.DefaultPolicy = UserPolicy ?? options.DefaultPolicy;
});
services.AddMicrosoftIdentityConsentHandler();
return services;
}
I have updated to the latest assemblies, but still get the same issue. The only way I can see to fix it at present is to somehow know that HandleException
hasn't redirected, delete the client cookies, then retry... but as we know, the first two steps are not possible from the client.
I have enabled verbose logging for MSAL, which produces the following output when attempting to connect to the app (sanitised):
31/03/2023 11:01:34.170 +01:00 [INF] Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager | User profile is available. Using 'C:\Users\ME\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. | {"EventId":{"Id":63,"Name":"UsingProfileAsKeyRepositoryWithDPAPI"}}
31/03/2023 11:01:35.701 +01:00 [INF] Microsoft.Hosting.Lifetime | Now listening on: https://[::]:12343 | {"EventId":{"Id":14,"Name":"ListeningOnAddress"}}
31/03/2023 11:01:35.705 +01:00 [INF] Microsoft.Hosting.Lifetime | Now listening on: https://localhost:12343 | {"EventId":{"Id":14,"Name":"ListeningOnAddress"}}
31/03/2023 11:01:35.712 +01:00 [INF] Microsoft.Hosting.Lifetime | Application started. Press Ctrl+C to shut down. | {}
31/03/2023 11:01:35.737 +01:00 [INF] Microsoft.Hosting.Lifetime | Hosting environment: Development | {}
31/03/2023 11:01:35.740 +01:00 [INF] Microsoft.Hosting.Lifetime | Content root path: D:\Git\Clients\COMPANYNAME\AzureRepos\COMPANYNAME.APPNAME\src-BlazorApp\COMPANYNAME.APPNAME.Web.App | {}
31/03/2023 11:01:38.429 +01:00 [INF] Microsoft.AspNetCore.Hosting.Diagnostics | Request starting HTTP/2 GET https://localhost:12343/ - - | {"Protocol":"HTTP/2","Method":"GET","ContentType":null,"ContentLength":null,"Scheme":"https","Host":"localhost:12343","PathBase":"","Path":"/","QueryString":"","EventId":{"Id":1},"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:38.790 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:38Z] ConfidentialClientApplication 38350037 created | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:38.834 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:38Z - 6ceb106f-6438-421e-9f0a-ad51ff4577a0] [Cache Session Manager] Entering the cache semaphore. Real semaphore: False. Count: 1 | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:38.835 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:38Z - 6ceb106f-6438-421e-9f0a-ad51ff4577a0] [Cache Session Manager] Entered cache semaphore | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:38.844 +01:00 [DBG] Microsoft.Identity.Web.TokenCacheProviders.Distributed.MsalDistributedTokenCacheAdapter | [MsIdWeb] MemoryCache: Read cacheKey 7e20173d-1a94-4a6d-a491-f6d2e4580b37.a8a2a680-e1fa-4c90-9702-31d178ccc348 cache size 0 | {"EventId":{"Id":106,"Name":"MemoryCacheRead"},"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.162 +01:00 [DBG] Microsoft.Identity.Web.TokenCacheProviders.Distributed.MsalDistributedTokenCacheAdapter | [MsIdWeb] DistributedCache: Read cacheKey 7e20173d-1a94-4a6d-a491-f6d2e4580b37.a8a2a680-e1fa-4c90-9702-31d178ccc348 cache size 0 InRetry? false | {"EventId":{"Id":100,"Name":"DistributedCacheState"},"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.167 +01:00 [DBG] Microsoft.Identity.Web.TokenCacheProviders.Distributed.MsalDistributedTokenCacheAdapter | [MsIdWeb] DistributedCache: Read Time in MilliSeconds 317.6345 | {"EventId":{"Id":102,"Name":"DistributedCacheReadTime"},"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.170 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 6ceb106f-6438-421e-9f0a-ad51ff4577a0] [Cache Session Manager] Released cache semaphore | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.178 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 6ceb106f-6438-421e-9f0a-ad51ff4577a0] GetAccounts found 0 RTs and 0 accounts in MSAL cache. | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.180 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 6ceb106f-6438-421e-9f0a-ad51ff4577a0] [Region discovery] Not using a regional authority. | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.182 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 6ceb106f-6438-421e-9f0a-ad51ff4577a0] [Instance Discovery] Tried to use network cache provider for login.microsoftonline.com. Success? False. | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.183 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 6ceb106f-6438-421e-9f0a-ad51ff4577a0] [Instance Discovery] Tried to use known metadata provider for login.microsoftonline.com. Success? True. | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.183 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 6ceb106f-6438-421e-9f0a-ad51ff4577a0] GetAccounts found 0 RTs and 0 accounts in MSAL cache after environment filtering. | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.183 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 6ceb106f-6438-421e-9f0a-ad51ff4577a0] Filtered by home account id. Remaining accounts 0 | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.184 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z] Found 0 cache accounts and 0 broker accounts | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.188 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z] Returning 0 accounts | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.193 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3] MSAL MSAL.NetCore with assembly version '4.46.0.0'. CorrelationId(0a1f588b-7b7d-4663-88c4-59d594dee6c3) | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.196 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3] === AcquireTokenSilent Parameters === | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.198 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3] LoginHint provided: False | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.204 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3] Account provided: False | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.207 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3] ForceRefresh: False | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.213 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3]
=== Request Data ===
Authority Provided? - True
Scopes - api://COMPANYNAME.com/.default
Extra Query Params Keys (space separated) -
ApiId - AcquireTokenSilent
IsConfidentialClient - True
SendX5C - False
LoginHint ? False
IsBrokerConfigured - False
HomeAccountId - False
CorrelationId - 0a1f588b-7b7d-4663-88c4-59d594dee6c3
UserAssertion set: False
LongRunningOboCacheKey set: False
Region configured:
| {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.216 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3] === Token Acquisition (SilentRequest) started:
Scopes: api://COMPANYNAME.com/.default
Authority Host: login.microsoftonline.com | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.223 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3] No account passed to AcquireTokenSilent. | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.223 +01:00 [DBG] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3] Token cache could not satisfy silent request. | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.234 +01:00 [ERR] Microsoft.Identity.Web.TokenAcquisition | False MSAL 4.46.0.0 MSAL.NetCore .NET 6.0.15 Microsoft Windows 10.0.22621 [2023-03-31 10:01:39Z - 0a1f588b-7b7d-4663-88c4-59d594dee6c3] Exception type: Microsoft.Identity.Client.MsalUiRequiredException
, ErrorCode: user_null
HTTP StatusCode 0
CorrelationId
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) | {"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
31/03/2023 11:01:39.242 +01:00 [INF] Microsoft.Identity.Web.TokenAcquisition | [MsIdWeb] An error occured during token acquisition: No account or login hint was passed to the AcquireTokenSilent call. | {"EventId":{"Id":300,"Name":"TokenAcquisitionError"},"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K","ExceptionDetail":{"HResult":-2146233088,"Message":"No account or login hint was passed to the AcquireTokenSilent call. ","Source":"Microsoft.Identity.Client","TargetSite":"Void MoveNext()","Classification":"AcquireTokenSilentFailed","StatusCode":0,"Claims":null,"ResponseBody":null,"Headers":null,"CorrelationId":null,"IsRetryable":false,"ErrorCode":"user_null","Type":"Microsoft.Identity.Client.MsalUiRequiredException"}}
MSAL.NetCore.4.46.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.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, IEnumerable`1 scopes, String tenantId, 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:
31/03/2023 11:01:39.277 +01:00 [ERR] Microsoft.AspNetCore.Server.Kestrel | Connection id "0HMPHPRLKLS6K", Request id "0HMPHPRLKLS6K:00000001": An unhandled exception was thrown by the application. | {"EventId":{"Id":13,"Name":"ApplicationError"},"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ExceptionDetail":{"Type":"System.NullReferenceException","HResult":-2147467261,"Message":"Object reference not set to an instance of an object.","Source":"COMPANYNAME.APPNAME.Core.Client.Model.APPNAMEWebAPI","TargetSite":"Void MoveNext()"}}
System.NullReferenceException: Object reference not set to an instance of an object.
at COMPANYNAME.APPNAME.Core.Client.Model.APPNAMEWebAPI.Client.APPNAMEWebAPIClient.GetAPPNAMEIdentityAsync(APPNAMEIdentityRequest req) in D:\Git\Clients\COMPANYNAME\AzureRepos\COMPANYNAME.APPNAME\src-APPNAMEClient\COMPANYNAME.APPNAME.Core.Client.Model.APPNAMEWebAPI\Client\APPNAMEWebAPIClient.cs:line 168
at COMPANYNAME.APPNAME.Core.Client.Auth.AzureAD.AzureADAPPNAMEUserRequirementAuthorizationHandler.HandleRequirementAsync(AuthorizationHandlerContext context, AzureADAPPNAMEUserRequirement requirement) in D:\Git\Clients\COMPANYNAME\AzureRepos\COMPANYNAME.APPNAME\src-BlazorApp\COMPANYNAME.APPNAME.Core.Client.Auth\AzureAD\AzureADAPPNAMEUserRequirementAuthorizationHandler.cs:line 35
at Microsoft.AspNetCore.Authorization.AuthorizationHandler`1.HandleAsync(AuthorizationHandlerContext context)
at Microsoft.AspNetCore.Authorization.DefaultAuthorizationService.AuthorizeAsync(ClaimsPrincipal user, Object resource, IEnumerable`1 requirements)
at Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, Object resource)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at COMPANYNAME.APPNAME.Web.App.APPNAMERequestLocalizationCookiesMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in D:\Git\Clients\COMPANYNAME\AzureRepos\COMPANYNAME.APPNAME\src-BlazorApp\COMPANYNAME.APPNAME.Web.App\APPNAMERequestLocalizationCookiesMiddleware.cs:line 47
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware.InvokeAsync(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
31/03/2023 11:01:39.319 +01:00 [INF] Microsoft.AspNetCore.Hosting.Diagnostics | Request finished HTTP/2 GET https://localhost:12343/ - - - 500 0 - 892.4113ms | {"ElapsedMilliseconds":892.4113,"StatusCode":500,"ContentType":null,"ContentLength":0,"Protocol":"HTTP/2","Method":"GET","Scheme":"https","Host":"localhost:12343","PathBase":"","Path":"/","QueryString":"","EventId":{"Id":2},"RequestId":"0HMPHPRLKLS6K:00000001","RequestPath":"/","ConnectionId":"0HMPHPRLKLS6K"}
The last error is because the null
that is returned by our wrapper (the first bit of pasted code) is not ever expected to be used by the calling code... because the blazor app should have redirected by then.
This is difficult to replicate, I am only able to provide this right now because my workstation is in the correct state.
Further information: as soon as a clear the .AspNetCore.Cookies
cookie, the redirect works. If I then replace the value of the .AspNetCore.Cookies
cookie with the OLD cookie value, it continues to work, so I expect there is a conflict somewhere with the value cached in the Token cache database and the cookie value.
This is happening for me each time I relaunch visual studio. The cookie is bound to the session. So to counter-act this problem I create a GUID with each session and name the cookie after the GUID. That way the cookie is bound to the session also.
builder.Services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = Guid.NewGuid().ToString();
options.Cookie.IsEssential = false;
});
Same error on my project, which is using .Net 6 Blazor Server.
builder.Services
// Add support for OpenId authentication
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
// Microsoft identity platform web app that requires an auth code flow
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Prompt = "select_account";
options.Events.OnTokenValidated = async context =>
{
var tokenAcquisition = context.HttpContext.RequestServices
.GetRequiredService<ITokenAcquisition>();
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<IStartup>>();
var token = await tokenAcquisition
.GetAccessTokenForUserAsync(initialScopes!, user: context.Principal);
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (request) =>
{
await Task.Run(() =>
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
})
);
};
})
// Add ability to call Microsoft Graph APIs with specific permissions
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
// Enable dependency injection for GraphServiceClient
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
// Add token cache
// .AddInMemoryTokenCaches();
.AddDistributedTokenCaches();
It would worked when user login for the first time, then it will fail with error:user_null when page reloaded.
I guess the login action triggered the method to add user token in request, and fail when page reload does not add the token back into request header:
options.Events.OnTokenValidated = async context =>
{
var tokenAcquisition = context.HttpContext.RequestServices
.GetRequiredService<ITokenAcquisition>();
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<IStartup>>();
var token = await tokenAcquisition
.GetAccessTokenForUserAsync(initialScopes!, user: context.Principal);
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (request) =>
{
await Task.Run(() =>
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
})
);
};
So, can we have some configuration in the method .AddMicrosoftGraph()
to have the GraphServiceClient
always have the user token in the request header?
I have the same problem using he sample code. The exception occurs only when calling the second line of the code below.
var client = this.GetGraphServiceClient();
var me = await client.Me.GetAsync();'
This needs a proper solution for Blazor rather than a workaround. Still a pain to handle.
For Blazor (net9) adding:
builder.Services.AddMicrosoftIdentityConsentHandler();
did a job.
var user = await graph.Me.GetAsync();
started working on component
This issue is for a: (mark with an
x
)The issue was found for the following scenario:
Please add an 'x' for the scenario(s) where you found an issue
Repro-ing the issue
Repro steps
I've configured an App registration with delegated permission to create an onlineMeeting (onlineMeetings.ReadWrite). I've configured a .Net Core 5 app to call api in this way `services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi(new string[] { "user.read", "onlineMeetings.readWrite" }) .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) .AddDistributedTokenCaches();
Try to call the Graph api through
await graphServiceClient.Me.OnlineMeetings.Request().AddAsync(onlineMeeting).ConfigureAwait(false);
Expected behavior i am expecting the token to refresh what it's expired
Actual behavior but after token is expired i receive the error MsalUiRequiredException: No Account or login hint was passed to the AcquireSilent call. First login everything works fine and the token is correctly saved on the db
Possible Solution I followed all the threads opened and i've tried to use
[AuthorizeForScopes(Scopes = new[] { "<TheScopeThatYouArePassingOnAcquireTokenMethod>" })]
found here i've also tried to get the token manuallyvar pca = PublicClientApplicationBuilder.Create(clientId).WithRedirectUri("https://localhost:44341").Build();
but seems that the https doen not work infact i get the error:var pca = PublicClientApplicationBuilder.Create("07ee5b5a-dec2-4904-ae26-b2f2901e2f06").WithRedirectUri("https://localhost:44341").Build();
Additional context/ Error codes / Screenshots
Any log messages given by the failure
Add any other context about the problem here, such as logs. This is the log of the exception: `2021-09-23 09:43:29.0079|1|ERROR|Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware|An unhandled exception has occurred while executing the request. Status Code: 0 Microsoft.Graph.ServiceException: Code: generalException Message: An error occurred sending the request.
---> 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.36.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.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, IEnumerable
1 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 ---`
OS and Version?
Versions
Attempting to troubleshooting yourself:
Mention any other details that might be useful