Azure / azure-signalr

Azure SignalR Service SDK for .NET
https://aka.ms/signalr-service
MIT License
420 stars 99 forks source link

TokenAcquisition Exception since adding Azure Signal R Service #1875

Open johnnyjones09 opened 9 months ago

johnnyjones09 commented 9 months ago

I recently added the Azure Signal R service to my .NET 6 blazor server application. My application is connecting to the service successfully in azure and that part looks good. My application communicates with an API to retrieve its data, before calling the API I have a DelegatingHandler which gets a token from azure with the required scope for that API. Once retrieved it added the token to the headers and sends the request. This has worked fine until Azure Signal R was added. I'm now getting the following exception and don't understand why.

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

I also noticed one part of the application that retrieves a cookie from httpcontext is no longer working. Is this because SignalR doesnt have access to the httpcontext?

Below is my startup code and the code from the delegating handler

 using System.Net.Http.Headers;
 using System.Net;
 using Microsoft.Identity.Web;
 using System.Security.Claims;

namespace BlazorApp.Handlers
{
    public class AuthenticationDelegatingHandler : DelegatingHandler
    {
        private readonly ITokenAcquisition _tokenAcquisition;
        private readonly IConfiguration _configuration;
        private readonly ILogger<AuthenticationDelegatingHandler> _logger;

        public AuthenticationDelegatingHandler(ITokenAcquisition tokenAcquisition, IConfiguration configuration, ILogger<AuthenticationDelegatingHandler> logger)
        {                      
            _tokenAcquisition = tokenAcquisition ?? throw new ArgumentNullException(nameof(tokenAcquisition));
            _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var scopes = new[] { _configuration["ApiScope"] };
            string accessToken = string.Empty;

            try
            {
                accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes!);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error while fetching access token for communication with API. Details: {Message}. {InnerException}", ex.Message, ex.InnerException);
                throw;
            }                        

            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            var response = await base.SendAsync(request, cancellationToken);            
            return response;
        }
    }
}

Some key start up services being setup:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddLogging();
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(options =>
    {
        builder.Configuration.Bind("AzureAdB2C", options);        
    })
    .EnableTokenAcquisitionToCallDownstreamApi(new string[] { builder.Configuration["ApiScope"] })
    .AddInMemoryTokenCaches(); 

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
})
.AddMicrosoftIdentityUI();
builder.Services.AddRazorPages();
builder.Services.AddHttpContextAccessor();
builder.Services.AddServerSideBlazor().AddMicrosoftIdentityConsentHandler();
builder.Services.AddScoped<NotificationService>();
builder.Services.AddLocalization(opt => { opt.ResourcesPath = "Resources"; });
builder.Services.AddTransient<AuthenticationDelegatingHandler>();

builder.Services.AddHttpClient("WebAPI", client => client.BaseAddress = new Uri(builder.Configuration["ApiBaseAddress"] ?? string.Empty))
    .AddHttpMessageHandler<AuthenticationDelegatingHandler>();

builder.Services.AddHsts(options =>
{
    options.Preload = true;
    options.IncludeSubDomains = true;
    options.MaxAge = TimeSpan.FromDays(365);
});

builder.Services.AddHealthChecks();
RegisterTypes(builder.Services);
builder.Services.AddSignalR().AddAzureSignalR();

var app = builder.Build();
if (app.Environment.IsDevelopment() || builder.Environment.IsEnvironment("DEV"))
    app.UseDeveloperExceptionPage();    

app.UseHsts();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseRequestLocalization();
app.UseResponseHeaderProtection();
app.MapHealthChecks("/health");
app.MapControllers();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});

app.Run();

Currently using Microsoft.Azure.SignalR - Version="1.22.0"

Thanks

vicancy commented 8 months ago

I also noticed one part of the application that retrieves a cookie from httpcontext is no longer working. Is this because SignalR doesnt have access to the httpcontext?

When using Azure SignalR, HttpContext inside the Hub is reconstructed by Azure SignalR SDK. And since Azure SignalR is another domain, cookie is not passed through Azure SignalR to your app server, and so, cookie is no longer working after the HttpContext is reconstructed from Azure SignalR SDK.

When is this "WebAPI" httpclient used? Would you mind sharing with me a minimum repro-able project?