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
@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: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 anHttpClient
with the Bearer token already already applied:The
NullReferenceException
happens when that middleware runs. Take this snippet as an example:The call to
AcquireTokenAsync
works fine. The call toApiCustomersGetAsync
(which is just Swagger-generated boilerplate to make an HTTP GET call to the downstream API) calls theBearerTokenRequestHandler
, 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.