AzureAD / microsoft-identity-web

Helps creating protected web apps and web APIs with Microsoft identity platform and Azure AD B2C
MIT License
671 stars 208 forks source link

[Bug] NullReferenceException when using Azure SignalR in server-side Blazor app #392

Closed DanielMannInCycle closed 4 years ago

DanielMannInCycle commented 4 years ago

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?

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
jennyf19 commented 4 years ago

thanks @DanielMannInCycle

DanielMannInCycle commented 4 years ago

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.

jmprieur commented 4 years ago

Thanks for the update, @DanielMannInCycle

DanielMannInCycle commented 4 years ago

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

jennyf19 commented 4 years ago

@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

DanielMannInCycle commented 4 years ago

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

dansmitt commented 4 years ago

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

jennyf19 commented 4 years ago

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

jennyf19 commented 4 years ago

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

dansmitt commented 4 years ago

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

jennyf19 commented 4 years ago

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

dansmitt commented 4 years ago

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

DanielMannInCycle commented 4 years ago

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

jennyf19 commented 4 years ago

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

DanielMannInCycle commented 4 years ago

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

jennyf19 commented 4 years ago

@DanielMannInCycle can you send me an email - jeferrie@microsoft.com ? thanks. :)

jennyf19 commented 4 years ago

Included in 0.2.2-preview release