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

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
    • 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

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 17

Most upvoted comments

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

@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:

                .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",...)?

@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]

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

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

@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?

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

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.