admin 管理员组

文章数量: 1086019

I'm building a Blazor Web App (.NET 8) using Server interactivity and authenticating users via Microsoft Entra ID. Authentication works — I see the user is logged in, and their claims (oid, tid, etc.) are present.

My problems comes when I try to call the API from the blazor app:

var token = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes, user);

The error from the above call is

MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call

Verbose MSAL logs say:

Found 0 accounts in MSAL cache

No refresh tokens or accounts found

Token cache could not satisfy silent request

This happens even though the user is authenticated.

My setup:

// Program.cs

builder.Services.AddBlazorServerAuthentication(builder.Configuration);
builder.Services.AddBlazorServerAuthorization();

// Add services to the container.
_ = builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddMicrosoftIdentityConsentHandler();

_ = builder.Services.AddServerSideBlazor()
    .AddMicrosoftIdentityConsentHandler();

_ = builder.Services.AddHttpContextAccessor();
_ = builder.Services.AddCascadingAuthenticationState();

 = builder.Services.AddTransient<AccessTokenHandler>();

_ = builder.Services
    .AddHttpClient("WebApiClient", client => client.BaseAddress = builder.Configuration.GetValue<Uri>("DownstreamApi:BaseUrl"))
    .AddHttpMessageHandler<AccessTokenHandler>();
public static void AddBlazorServerAuthentication(this IServiceCollection services, IConfiguration configuration)
{
    JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
    string[] scopes = configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(';');

    _ = services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddBearerToken()
        .AddMicrosoftIdentityWebApp(options =>
        {
            configuration.Bind("AzureAd", options);

            options.MaxAge = TimeSpan.FromHours(8);
            options.AutomaticRefreshInterval = TimeSpan.FromMinutes(15);

            options.Events.OnRemoteFailure = async context =>
            {
                try
                {
                    AuthenticationProperties authenticationProperties = new()
                    {
                        RedirectUri = "/"
                    };
                    authenticationProperties.Parameters["logoutHint"] = context?.HttpContext?.User?.Identity?.Name;

                    await context.HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, authenticationProperties);
                    await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

                    Console.WriteLine("Authentication failed. Logging out user silently");

                    context.HandleResponse();
                    await Task.CompletedTask;
                }
                catch (Exception)
                {
                    throw;
                }
            };
        })
        .EnableTokenAcquisitionToCallDownstreamApi(scopes)
        .AddInMemoryTokenCaches();
}

public static void AddBlazorServerAuthorization(this IServiceCollection services)
{
    _ = services.AddAuthorization(options =>
    {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
    });
}
//AccessTokenHandler

public class AccessTokenHandler : DelegatingHandler
{
    private readonly ITokenAcquisition _tokenAcquisition;
    private readonly IConfiguration _config;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public AccessTokenHandler(ITokenAcquisition tokenAcquisition, IConfiguration config, IHttpContextAccessor httpContextAccessor)
    {
        _tokenAcquisition = tokenAcquisition;
        _config = config;
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        string[] scopes = _config["DownstreamApi:Scopes"]?.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        if (scopes?.Length > 0)
        {
            string token = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes,
                user: _httpContextAccessor.HttpContext?.User,
                authenticationScheme: OpenIdConnectDefaults.AuthenticationScheme);
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}
"AzureAd": {
    "Instance": "/",
    "ClientId": "xxxxxxx",
    "ClientSecret": "xyxyxyxyxy",
    "TenantId": "zzzzzzzzz"
},
"DownstreamApi": {
    "BaseUrl": "https://localhost:44385",
    "Scopes": "api://yyyyyyyyyy/access_as_user"
}

本文标签: Blazor Web App (Server) EntraID authentication and downstream ApiStack Overflow