Microsoft-identity-web: [Bug] NullReferenceException when using Azure SignalR in server-side Blazor app

Created on 3 Aug 2020  路  17Comments  路  Source: AzureAD/microsoft-identity-web

Which version of Microsoft Identity Web are you using?
Note that to get help, you need to run the latest version.

0.2.1-preview

Where is the issue?

  • Web app

    • [ ] Sign-in users

    • [x] Sign-in users and call web APIs

  • Web API

    • [ ] Protected web APIs (validating tokens)

    • [ ] Protected web APIs (validating scopes)

    • [ ] Protected web APIs call downstream web APIs

  • Token cache serialization

    • [ ] In-memory caches

    • [ ] Session caches

    • [ ] Distributed caches

  • Other (please describe)

Is this a new or an existing app?

This is an existing application. I upgraded to the latest version of the Microsoft Identity Web library. It works with self-hosted SignalR, but fails when integrated with Azure SignalR, presumably due to missing HttpContext.

Repro
I don't have a minimal repro. I am currently attempting to put one together; I will update this issue with a link to a repo if I'm successful.

Expected behavior
Azure SignalR works.

Actual behavior
NullReferenceException is thrown

Additional context / logs / screenshots
We are registering the identity provider as follows:
Startup.cs:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions();

            _authenticationStrategy.AddAuthentication(services, Configuration);
            services.AddTransient<IAuthenticationStrategy>(provider => _authenticationStrategy);

            if (!Env.IsDevelopment() || !string.IsNullOrEmpty(Configuration.GetValue<string>("MsalDistributedTokenCache")))
            {
                services.AddStackExchangeRedisCache(options =>
                {
                    options.Configuration = Configuration.GetValue<string>("MsalDistributedTokenCache");
                });
                services.AddDistributedTokenCaches();
            }

            services.AddControllersWithViews(options =>
            {
                var policy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();

                options.Filters.Add(new AuthorizeFilter(policy));
            })
                .AddMicrosoftIdentityUI();

            services.AddAuthorizationCore(authzOptions =>
            {
                foreach (var policyConfig in Configuration.GetSection("ElevatePolicies").GetChildren())
                {
                    var policyMapping = policyConfig.Get<AuthorizationPolicyGroupMapping>();
                    authzOptions.AddPolicy(policyMapping.Policy, builder => builder.RequireAssertion(context =>
                    {
                        return context.User.HasClaim(c => c.Type == policyMapping.ClaimType && policyMapping.ClaimValues.Any(g => g == c.Value));
                    }));
                }
            });

            services.AddApplicationInsightsTelemetry();

            //Razor and Blazor
            services.AddRazorPages();
            services.AddServerSideBlazor()
                .AddMicrosoftIdentityConsentHandler();

            services.AddHttpClient();

            // register scoped core services            
            services.AddScoped<AppState>();

            services.AddScoped<ITokenService, TokenService>();
            services.AddScoped<BusyState>();
            services.AddScoped<BeforeUnloadAdapter>();

            services.AddBlazoredToast();
            services.AddExceptionHandling();

            services.AddSingleton<PersonService>();
            services.AddSingleton<IUserProfileService, UserProfileService>();

            services.AddSingleton<IDashboardTileFeature, DashboardTileFeature>();
            services.AddSingleton<IAcceleratorsDashboardTileFeature, AcceleratorsDashboadTileFeature>();
            services.AddSingleton<INavigationItemFeature, NavigationItemFeature>();
            services.AddSingleton<IShellExtensionService, ShellExtensionsService>();

            services.AddSettings<CustomerThemeProviderSettings>(this.Configuration);
            services.AddSettings<LandingPageRedirectSettings>(this.Configuration);
            services.AddTransient<ICustomerThemeProvider, CustomerThemeProvider>();

            services.AddHealthChecks()
            .AddCheck("assemblies",
                new ExtensionsHealthCheckOptions(ShellExtensionExtensions.LoadedShellExtensions), null, new string[] { DevelopmentHealthCheckOptions.DevelopmentOnlyHealthCheckTag })
            .AddCheck("services", new ServiceProviderHealthCheckOptions(services), null,
                new string[] { DevelopmentHealthCheckOptions.DevelopmentOnlyHealthCheckTag });

            services.AddTelerikBlazor();
            services.AddSignalR()
                .AddAzureSignalR()
                ;
        }

The authentication strategies being registered are AzureAD and AzureADB2C. They are basically the same implementation:

public class AzureB2CAuthenticationStrategy : AuthenticationStrategy
    {
        public override string Name => "AzureAdB2C";

        public override string Scheme => AzureADB2CDefaults.OpenIdScheme;

        public override Task AddAuthentication(IServiceCollection services, IConfiguration configuration)
        {
            var initialScopes = (IEnumerable<string>)new string[] { "email" };
            var scopesFromConfiguration = configuration.GetValue<string>("AzureAdB2CSettings:Scopes");

            if (!string.IsNullOrEmpty(scopesFromConfiguration))
            {
                initialScopes = scopesFromConfiguration.Split(' ');
            }

            services.AddDistributedMemoryCache();
            services.AddAuthentication(Scheme)
                .AddMicrosoftWebApp(configuration, "AzureAdB2CSettings", Scheme, CookieScheme, false)
                .AddMicrosoftWebAppCallsWebApi(configuration, initialScopes, configSectionName: "AzureAdB2CSettings", Scheme)
                .AddInMemoryTokenCaches();

            services.Configure<OpenIdConnectOptions>(configuration.GetSection("AzureAdB2CSettings"));

            this.ConfigureCookieAuthenticationOptions(services);

            return Task.CompletedTask;
        }
    }
}

Here is the code for some of the methods that feature in the stack trace of the exception:

    public class TokenService : ITokenService
    {
        private readonly ITokenAcquisition tokenAcquisition;

        public TokenService(ITokenAcquisition tokenAcquisition)
        {
            this.tokenAcquisition = tokenAcquisition ?? throw new ArgumentNullException(nameof(tokenAcquisition));
        }

        public async Task<string> AcquireTokenAsync(IEnumerable<string> scopes)
        {
            try
            {
                return await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
            }
            catch (MsalUiRequiredException msalEx) when (msalEx.Classification == UiRequiredExceptionClassification.AcquireTokenSilentFailed)
            {
                throw new TokenCacheUnavailableException(msalEx);
            }
        }
    }
public class BearerTokenRequestHandler : DelegatingHandler
    {
        private readonly ITokenService _tokenService;
        private readonly ClientSettings _clientSettings;

        public BearerTokenRequestHandler(ITokenService tokenService, ClientSettings clientSettings)
        {
            _tokenService = tokenService;
            _clientSettings = clientSettings;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var token = await _tokenService.AcquireTokenAsync(_clientSettings.EngagementsFeatureServiceSettings.Scopes?.Split(','));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);

            var response = await base.SendAsync(request, cancellationToken);
            return response;
        }
    }

When running, the initial token retrieval appears to be successful but fails when retrieving the token for usage by a down-stream API:

    System.Private.CoreLib.dll!System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()   Unknown
    System.Private.CoreLib.dll!System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task task) Unknown
    System.Private.CoreLib.dll!System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task task)    Unknown
    System.Private.CoreLib.dll!System.Runtime.CompilerServices.TaskAwaiter<System.__Canon>.GetResult()  Unknown
    [Waiting on Async Operation, double-click or press enter to view Async Call Stacks] 
>   Elevate.Shell.Server.dll!Elevate.Shell.Server.Authentication.TokenService.AcquireTokenAsync(System.Collections.Generic.IEnumerable<string> scopes) Line 24  C#
    Elevate.Shell.Extensions.Engagements.dll!Elevate.Shell.Extensions.Engagements.BearerTokenRequestHandler.SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) Line 61 C#
    System.Net.Http.dll!System.Net.Http.DelegatingHandler.SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)   Unknown
    Microsoft.Extensions.Http.dll!Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)  Unknown
    System.Net.Http.dll!System.Net.Http.DelegatingHandler.SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)   Unknown
    System.Net.Http.dll!System.Net.Http.HttpMessageInvoker.SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)  Unknown
    System.Net.Http.dll!System.Net.Http.HttpClient.SendAsync(System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpCompletionOption completionOption, System.Threading.CancellationToken cancellationToken)   Unknown
    Elevate.Shell.Extensions.Engagements.dll!Elevate.Shell.Extensions.Engagements.Services.EngagementsFeatureService.ApiEngagementsGetAsync(System.Threading.CancellationToken cancellationToken) Line 693  C#
    Elevate.Shell.Extensions.Engagements.dll!Elevate.Shell.Extensions.Engagements.Components.EngagementsList.OnInitializedAsync() Line 84   C#
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters)  Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(Microsoft.AspNetCore.Components.ParameterView parameters)  Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(ref Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.DiffContext diffContext, int frameIndex)  Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(ref Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.DiffContext diffContext, int frameIndex) Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(ref Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.DiffContext diffContext, int newFrameIndex)    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(ref Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl)    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Microsoft.AspNetCore.Components.RenderTree.Renderer renderer, Microsoft.AspNetCore.Components.Rendering.RenderBatchBuilder batchBuilder, int componentId, Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame> oldTree, Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame> newTree)    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(Microsoft.AspNetCore.Components.Rendering.RenderBatchBuilder batchBuilder, Microsoft.AspNetCore.Components.RenderFragment renderFragment)  Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(Microsoft.AspNetCore.Components.Rendering.RenderQueueEntry renderQueueEntry)  Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()  Unknown
    Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer.ProcessPendingRender()    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment)    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged() Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters)  Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(Microsoft.AspNetCore.Components.ParameterView parameters)  Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(int componentId, Microsoft.AspNetCore.Components.ParameterView initialParameters)  Unknown
    Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer.AddComponentAsync(System.Type componentType, Microsoft.AspNetCore.Components.ParameterView parameters, string domElementSelector) Unknown
    Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.InitializeAsync.AnonymousMethod__0() Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.InvokeAsync.AnonymousMethod__9_0(object state) Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteSynchronously(System.Threading.Tasks.TaskCompletionSource<object> completion, System.Threading.SendOrPostCallback d, object state)  Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteSynchronouslyIfPossible(System.Threading.SendOrPostCallback d, object state)    Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.InvokeAsync(System.Func<System.Threading.Tasks.Task> asyncAction)  Unknown
    Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContextDispatcher.InvokeAsync(System.Func<System.Threading.Tasks.Task> workItem)   Unknown
    Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.InitializeAsync(System.Threading.CancellationToken cancellationToken)    Unknown
    Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.ComponentHub.StartCircuit(string baseUri, string uri, string serializedComponentRecords)  Unknown
    [Lightweight Function]  
    Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.Extensions.Internal.ObjectMethodExecutor.ExecuteAsync(object target, object[] parameters)   Unknown
    Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher<Microsoft.AspNetCore.Components.Server.ComponentHub>.ExecuteHubMethod(Microsoft.Extensions.Internal.ObjectMethodExecutor methodExecutor, Microsoft.AspNetCore.Components.Server.ComponentHub hub, object[] arguments)  Unknown
    Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher<Microsoft.AspNetCore.Components.Server.ComponentHub>.Invoke.__ExecuteInvocation|0()    Unknown
    Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher<Microsoft.AspNetCore.Components.Server.ComponentHub>.Invoke(Microsoft.AspNetCore.SignalR.Internal.HubMethodDescriptor descriptor, Microsoft.AspNetCore.SignalR.HubConnectionContext connection, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage hubMethodInvocationMessage, bool isStreamResponse, bool isStreamCall) Unknown
    Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher<Microsoft.AspNetCore.Components.Server.ComponentHub>.ProcessInvocation(Microsoft.AspNetCore.SignalR.HubConnectionContext connection, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage hubMethodInvocationMessage, bool isStreamResponse)   Unknown
    Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher<Microsoft.AspNetCore.Components.Server.ComponentHub>.DispatchMessageAsync(Microsoft.AspNetCore.SignalR.HubConnectionContext connection, Microsoft.AspNetCore.SignalR.Protocol.HubMessage hubMessage)   Unknown
    Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.HubConnectionHandler<Microsoft.AspNetCore.Components.Server.ComponentHub>.DispatchMessagesAsync(Microsoft.AspNetCore.SignalR.HubConnectionContext connection)    Unknown
    [Resuming Async Method] 
    System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.AsyncStateMachineBox<Microsoft.AspNetCore.SignalR.HubConnectionHandler<Microsoft.AspNetCore.Components.Server.ComponentHub>.<DispatchMessagesAsync>d__15>.ExecutionContextCallback(object s)   Unknown
    System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread threadPoolThread, System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)   Unknown
    System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.AsyncStateMachineBox<Microsoft.AspNetCore.SignalR.HubConnectionHandler<Microsoft.AspNetCore.Components.Server.ComponentHub>.<DispatchMessagesAsync>d__15>.MoveNext(System.Threading.Thread threadPoolThread)   Unknown
    System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.AsyncStateMachineBox<Microsoft.AspNetCore.SignalR.HubConnectionHandler<System.__Canon>.<DispatchMessagesAsync>d__15>.ExecuteFromThreadPool(System.Threading.Thread threadPoolThread)   Unknown
    System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()  Unknown
    System.Private.CoreLib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()   Unknown
    [Async Call Stack]  
    [Async] Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.HubConnectionHandler<Microsoft.AspNetCore.Components.Server.ComponentHub>.RunHubAsync(Microsoft.AspNetCore.SignalR.HubConnectionContext connection)  Unknown
    [Async] Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.HubConnectionHandler<Microsoft.AspNetCore.Components.Server.ComponentHub>.OnConnectedAsync(Microsoft.AspNetCore.Connections.ConnectionContext connection)    Unknown
    [Async] Microsoft.Azure.SignalR.dll!Microsoft.Azure.SignalR.ServiceConnection.ProcessApplicationTaskAsyncCore(Microsoft.Azure.SignalR.ClientConnectionContext connection)   Unknown
    [Async] System.Private.CoreLib.dll!System.Threading.Tasks.TaskFactory.ContinueWhenAny   Unknown
    [Async] Microsoft.Azure.SignalR.dll!Microsoft.Azure.SignalR.ServiceConnection.ProcessIncomingMessageAsync(Microsoft.Azure.SignalR.ClientConnectionContext connection)   Unknown
    [Async] System.Private.CoreLib.dll!System.Threading.Tasks.TaskFactory.ContinueWhenAny   Unknown
    [Async] Microsoft.Azure.SignalR.dll!Microsoft.Azure.SignalR.ServiceConnection.ProcessClientConnectionAsync(Microsoft.Azure.SignalR.ClientConnectionContext connection)  Unknown
Blazor bug fixed

Most helpful comment

@jennyf19 works fine. Daniel, Javier and me verified that in the meeting.

All 17 comments

thanks @DanielMannInCycle

I'm still working on paring things down for a MVCE, but I'm about 90% sure I've identified the scenario that triggers this:

We have a DelegatingHandler middleware that is responsible for constructing an HttpClient with the Bearer token already already applied:

public class BearerTokenRequestHandler : DelegatingHandler
    {
        private readonly ITokenService _tokenService;
        private readonly ClientSettings _clientSettings;

        public BearerTokenRequestHandler(ITokenService tokenService, ClientSettings clientSettings)
        {
            _tokenService = tokenService;
            _clientSettings = clientSettings;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var token = await _tokenService.AcquireTokenAsync(_clientSettings.EngagementsFeatureServiceSettings.Scopes?.Split(','));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);

            var response = await base.SendAsync(request, cancellationToken);
            return response;
        }
    }

The NullReferenceException happens when that middleware runs. Take this snippet as an example:

    protected override async Task OnInitializedAsync()
    {
        var token = tokensvc.AcquireTokenAsync(new[] { "https://[redacted].onmicrosoft.com/[also-redacted]/[super-duper-redacted]" });
        var x = await EngagementsFeatureService.ApiCustomersGetAsync();
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }

The call to AcquireTokenAsync works fine. The call to ApiCustomersGetAsync (which is just Swagger-generated boilerplate to make an HTTP GET call to the downstream API) calls the BearerTokenRequestHandler, which dies with the NRE.

I haven't done a side-by-side comparison to figure out what's falling through in the Identity.Web code that shouldn't be, but I plan on doing so tomorrow.

Thanks for the update, @DanielMannInCycle

I have put the repro code into a private repo. I have added both @jennyf19 and @jmprieur to the repo.

https://github.com/DanielMannInCycle/identityrepro

@DanielMannInCycle I checked with ASP .NET Core team and here is their guidance for this:
"The HttpContext is not "fully" available when the user uses the SignalR service. That's one of the reasons why our guidance on Blazor is to completely avoid its usage. SignalR Service will try to "mock" some of the properties of the HTTP Context, but in general it is not going to work well. Our guidance to users is to capture the HttpContext data explicitly and pass it to the Blazor application as parameters to the root component."

Here is a sample that might help: https://github.com/javiercn/blazor-server-aad-sample/blob/master/BlazorServerAuthWithAzureActiveDirectory/Pages/_Host.cshtml#L23-L33 and https://github.com/javiercn/DetectServerSideBlazor/blob/master/BlazorApp1/Startup.cs#L109-L113

cc: @jmprieur

@jennyf19 Thanks for the information. However, I'm a bit confused. My understanding is that an external SignalR hub is required for scaling a Blazor server-side app -- please correct me if I'm wrong. If there's nothing that can be done to fix this library to work for a Blazor server-side app backed by external SignalR, then wouldn't it be appropriate to say that this library is not compatible with Blazor Server-Side?

@jennyf19 @jmprieur this is exactly this, what I wrote you about the Blazor SignalR problem.

@DanielMannInCycle I used the repo you sent, and had to make a few changes to get it working, but I'm not able to repro your issue now. I'm going to have @javiercn take a look as well.

In Startup.cs, I commented this:
//_authenticationStrategy.AddAuthentication(services, Configuration);

and added this, as you'll need to add Microsoft Identity Web explicitly:

string[] scopes = Configuration.GetValue<string>("CalledApi:CalledApiScopes")?.Split(' ');
services.AddMicrosoftWebAppAuthentication(Configuration, "AzureAd")
        .AddMicrosoftWebAppCallsWebApi(Configuration, scopes, "AzureAd")

Then i had to connect the Microsoft Identity Web UI, so we can pick up the challenge (which I saw is getting returned correctly).

app.UseEndpoints(endpoints =>
{
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
        endpoints.MapControllers();  //added this for our UI and the challenge
 });

in FetchData, i had to add a try/catch and then process the challenge:

protected override async Task OnInitializedAsync()
{
        try
        {
            var token = await tokensvc.AcquireTokenAsync(Program.Scopes);
            var x = await EngagementsFeatureService.ApiCustomersGetAsync();

            forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
        }
        catch( Exception ex)
        {
            ConsentHandler.HandleException(ex);
        }
}

The NavigationManager is not null. We are actively looking at this. Will keep you posted.

@DanielMannInCycle any update on this? Make sure you also delete your local nuget cache to make sure it's picking up the right build... @schmid37 i think this is working for you now, correct? any advice?

@jennyf19 works fine. Daniel, Javier and me verified that in the meeting.

@schmid37 awesome! so this issue can close once we release w/the changes from Javier?

@jennyf19 think so. You're about to release the 0.2.3 this week?

@jennyf19 I'm still trying to get it to work -- I'm still seeing a null NavigationManager.

@schmid37 we are testing the release now, would be 0.2.2-preview, so probably available tomorrow, Monday at the latest.

@DanielMannInCycle did you delete the local nuget cache, to make sure you're picking up the build w/the fix?
[typically on Windows under \users\you.nuget\Packages]

@jennyf19 I cloned the repo and added project references to ensure I'm picking up the latest changes. I cannot get the changes you made to my repro application to work properly. The line you commented out is necessary -- we use a strategy pattern to determine whether to use AzureAD or B2C.

If I change the AzureB2CAuthenticationStrategy.cs file to include this snippet:

services.AddMicrosoftWebAppAuthentication(configuration, "AzureAdB2CSettings", Scheme, CookieScheme, false) .AddMicrosoftWebAppCallsWebApi(configuration, initialScopes, "AzureAdB2CSettings") .AddInMemoryTokenCaches();

I still see a null NavigationManager. I'm not sure what the difference between the two methods of service registration are, so I'm at a loss with how to implement your changes properly within the context of the strategy pattern.

I've also seen this exception pop up in some situations, not sure why:
InvalidOperationException: No authentication handler is registered for the scheme 'OpenIdConnect'. The registered schemes are: Cookies, AzureADB2COpenID. Did you forget to call AddAuthentication().Add[SomeAuthHandler]("OpenIdConnect",...)?

@DanielMannInCycle can you send me an email - [email protected] ? thanks. :)

Included in 0.2.2-preview release

Was this page helpful?
0 / 5 - 0 ratings