I have a .NET 8 Blazor web app (Server) application that I have integrated with Microsoft.Identity.Web
(Entra ID). I can authenticate just fine, but while trying to implement the retrieval of an access token, I get MSAL errors.
I am using token caching (session) and initially testing the retrieval of the token in the OnInitializedAsync
of a page. Here's the thing, if I were to clear the cookies and cache of the local web application, it runs fine. As soon as I restart the application from the IDE, it produces the error:
MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
Microsoft.Identity.Client.Internal.Requests.Silent.SilentRequest.ExecuteAsync(CancellationToken cancellationToken)
Microsoft.Identity.Client.Internal.Requests.RequestBase+<>c__DisplayClass11_1+<b__1>d.MoveNext()
Microsoft.Identity.Client.Utils.StopwatchService.MeasureCodeBlockAsync(Func codeBlock)
Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
Microsoft.Identity.Client.ApiConfig.Executors.ClientApplicationBaseExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenSilentParameters silentParameters, CancellationToken cancellationToken)
Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForWebAppWithAccountFromCacheAsync(IConfidentialClientApplication application, ClaimsPrincipal claimsPrincipal, IEnumerable scopes, string tenantId, MergedOptions mergedOptions, string userFlow, TokenAcquisitionOptions tokenAcquisitionOptions)
System.Threading.Tasks.ValueTask.get_Result()
Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable scopes, string authenticationScheme, string tenantId, string userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
Here is some of the relevant code:
program.cs
:
using Microsoft.AspNetCore.Authentication.Cookies;using Microsoft.AspNetCore.Authentication.OpenIdConnect;using Microsoft.AspNetCore.Components.Authorization;using Microsoft.AspNetCore.Components.WebAssembly.Authentication;using Microsoft.Graph;using Microsoft.Identity.Client;using Microsoft.Identity.Web;using Microsoft.Identity.Web.TokenCacheProviders.Distributed;using Microsoft.Identity.Web.UI;using SFTownCenter.Components;using SFTownCenter.Services;using SFTownCenter.Services.Interfaces;using Syncfusion.Blazor;using Syncfusion.Blazor.Popups;using WebApplication = Microsoft.AspNetCore.Builder.WebApplication;var builder = WebApplication.CreateBuilder(args);// Load common settings (appsettings.json)builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);// Get the current environment name (Development, Staging, Production, etc.)var environment = builder.Environment.EnvironmentName; builder.Configuration.AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true);// Add services to the container.builder.Services.AddRazorComponents() .AddInteractiveServerComponents();builder.Services.AddRazorPages();builder.Services.AddSession(options =>{ options.Cookie.IsEssential = true; // Make the session cookie essential options.Cookie.SameSite = SameSiteMode.Lax; // Configure SameSite for session cookies options.IdleTimeout = TimeSpan.FromMinutes(30);});var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split('') ?? builder.Configuration["MicrosoftGraph:Scopes"]?.Split('');builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("EntraID")) .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph")) .AddSessionTokenCaches();builder.Services.AddControllersWithViews() .AddMicrosoftIdentityUI();builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));builder.Services.AddApiAuthorization();builder.Services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.Events.OnRedirectToAccessDenied = context => { context.Response.Redirect("/AccessDenied"); return context.Response.CompleteAsync(); }; });builder.Services.AddScoped<GraphService>();var app = builder.Build();// Configure the HTTP request pipeline.if (!app.Environment.IsDevelopment()){ app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts();}app.UseHttpsRedirection();app.UseSession();app.UseStaticFiles();app.UseAntiforgery();app.UseAuthentication();app.UseAuthorization();app.MapRazorComponents<App>() .AddInteractiveServerRenderMode();app.MapRazorPages();app.Run();
Razor Page, @attribute [Authorize]
is at the top of the razor page.
protected override async Task OnInitializedAsync(){ var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); User = authState.User.FindFirst(c => c.Type == System.Security.Claims.ClaimTypes.Name)?.Value ?? authState.User.FindFirst(c => c.Type == "name")?.Value; if (authState.User.Identity.IsAuthenticated) { string[] scopes = { "User.Read" }; var token = await TokenAcquisition.GetAccessTokenForUserAsync(scopes); }}
When I set break point in the OnInitializedAsync
, I can verify that the user is authenticated in the if statement. But whenever TokenAcquisition.GetAccessTokenForUserAsync
is called, the MSAL error is triggered.
I can also verify their is cookies and a session in developer console:
I have several changes in this code to include distributed memory caching, changing the call to happen in OnAfterRenderAsync
, and more and can't get it to work. It only works when I manually clear cookies and cache. Am I missing something?