Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] Looping redirect with Azure Signalr Service #573

Closed
jassent opened this issue Sep 12, 2020 · 13 comments
Closed

[Bug] Looping redirect with Azure Signalr Service #573

jassent opened this issue Sep 12, 2020 · 13 comments
Assignees
Labels
Azure SignalR Service Blazor bug Something isn't working duplicate This issue or pull request already exists fixed P1
Milestone

Comments

@jassent
Copy link

jassent commented Sep 12, 2020

Which version of Microsoft Identity Web are you using?
Microsoft.Identity.Web (0.4.0-preview)

Where is the issue?

  • Web app
    [X] Sign-in users and call web APIs

Is this a new or an existing app?
This is a new app or an experiment.

Repro
This sample app demonstrates the bug and uses Microsoft.Identity.Web, Blazor Server, Azure Signalr Service and Microsoft Graph:
https://github.com/jassent/Example-Blazor-Graph-Microsoft-Identity-Web/tree/master/tests/BlazorServerCallsGraph

The goal is to add Azure Signalr Service. Adding the following code to startup.cs causes an endless looping redirect when calling Microsoft Graph once the Blazor Server app is using Azure Signalr Service.

services.AddSignalR().AddAzureSignalR();

Expected behavior
The expected behavior is to be able to use Microsoft.Identity.Web in a Blazor Server web app that uses Microsoft Azure Signalr Service.

Actual behavior

Build the app and visit: https://localhost:44314/showprofile

Web App works fine when Blazor Server is run self-hosted. Migrating the Web App to Azure Signalr Service causes the problem. Adding "services.AddSignalR().AddAzureSignalR();" to Startup.cs causes the web page calling the Graph Api to continuously reload repeatedly calling https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/token

There is an exception thrown in the visual studio debug window but no meaningful information:
Exception thrown: 'Microsoft.Graph.ServiceException' in System.Private.CoreLib.dll System.Net.Http.HttpClient.Default.LogicalHandler: Information: Start processing HTTP request POST https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/token

System.Net.Http.HttpClient.Default.ClientHandler: Information: Sending HTTP request POST https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/token

System.Net.Http.HttpClient.Default.ClientHandler: Information: Received HTTP response after 201.8975ms - OK

System.Net.Http.HttpClient.Default.LogicalHandler: Information: End processing HTTP request after 209.1404ms - OK

The browser shows this error in the console window:
blazor.server.js:19 [2020-09-12T12:35:54.880Z] Error: Circuit has been shut down due to error.

Graph Api data is returned to the application between each login loop.

Additional context / logs / screenshots
Prior versions of the library 0.2.1-preview would produce a different error. The 0.2.1-preview version would allow the web app to call Graph Api, not cause a looping redirect but then would give a system exception (after a successful call to Graph Api) due to a null being returned from _tokenAcquisition.GetAccessTokenForUserAsync(_initialScopes)

@jassent
Copy link
Author

jassent commented Sep 13, 2020

I update the example repro in the original comment to be based on a project within the microsoft-identity-web repository. The example is a modified version of the BlazorServerCallsGraph test project.

Also, the downstreamAPI.CallWebApiForAppAsync() method works and does not cause the endless login looping. This is illustrated in the example.

@jennyf19
Copy link
Collaborator

@jassent This might be due to the fact because of Graph, the token acquisition is registered as a singleton, however, Blazor can only work with scoped services. This should be a workaround:

services.AddTokenAcquisition(false)

btw, false is the default. Let us know if this resolves the issue for you.

@jassent
Copy link
Author

jassent commented Sep 14, 2020

Thank you for the suggestion. I appreciate the assistance. I gave it a try and it didn't work for the /showprofile page. No data was returned by the graphclient and no looping redirect occurred. Instead an exception was generated. My hunch is that maybe the Azure SignalR Service doesn't like how Microsoft.Idenetity.Web is modifying the httpcontext?

After adding services.AddTokenAcquisition(false) to the startup.cs,

"user = await _graphServiceClient.Me.Request().GetAsync();" produces the following exception:

An unhandled exception occurred while processing the request.
InvalidOperationException: Cannot resolve scoped service 'Microsoft.Identity.Web.ITokenAcquisition' from root provider.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)

Stack Query Cookies Headers Routing
InvalidOperationException: Cannot resolve scoped service 'Microsoft.Identity.Web.ITokenAcquisition' from root provider.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
Microsoft.Extensions.DependencyInjection.ServiceProvider.Microsoft.Extensions.DependencyInjection.ServiceLookup.IServiceProviderEngineCallback.OnResolve(Type serviceType, IServiceScope scope)
Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<T>(IServiceProvider provider)
Microsoft.Identity.Web.MicrosoftGraphExtensions+<>c.<AddMicrosoftGraph>b__2_0(IServiceProvider serviceProvider)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor<TArgument, TResult>.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor<TArgument, TResult>.VisitCallSite(ServiceCallSite callSite, TArgument argument)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine+<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
Microsoft.AspNetCore.Components.ComponentFactory+<>c__DisplayClass5_0.<CreateInitializer>g__Initialize|2(IServiceProvider serviceProvider, IComponent component)
Microsoft.AspNetCore.Components.ComponentFactory.PerformPropertyInjection(IServiceProvider serviceProvider, IComponent instance)
Microsoft.AspNetCore.Components.ComponentFactory.InstantiateComponent(IServiceProvider serviceProvider, Type componentType)
Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(ref DiffContext diffContext, int newFrameIndex)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(ref DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, int componentId, ArrayRange<RenderTreeFrame> oldTree, ArrayRange<RenderTreeFrame> newTree)
Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(int componentId, RenderFragment renderFragment)
Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()
Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task)
Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)
Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(int componentId, ParameterView initialParameters)
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.RenderComponentAsync(Type componentType, ParameterView initialParameters)
Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext+<>c__11<TResult>+<<InvokeAsync>b__11_0>d.MoveNext()
Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.PrerenderComponentAsync(ParameterView parameters, HttpContext httpContext, Type componentType)
Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.RenderComponentAsync(ViewContext viewContext, Type componentType, RenderMode renderMode, object parameters)
Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, int i, int count)
blazor.Pages.Pages__Host.<ExecuteAsync>b__14_1() in _Host.cshtml
+
        <component type="typeof(App)" render-mode="ServerPrerendered" />
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
blazor.Pages.Pages__Host.ExecuteAsync() in _Host.cshtml
+
    Layout = null;
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)

@jmprieur
Copy link
Collaborator

@jassent

  1. Did you add services.AddTokenAcquisition(false) after the call to .AddMicrosoftGraph (that is all the normal code + services;AddTokenAcquisition()
  2. Which version of .NET framework do you use?

@jassent
Copy link
Author

jassent commented Sep 14, 2020

  1. Confirmed, I did add AddTokenAcquisition() after the call to AddMicrosoftGraph(). A copy of the startup.cs is below.

  2. The project is .net core 3.1. The development machine has .NET Full 4.8.04084 and .NET Core 3.1.402. The project is referencing the Microsoft.Identity.Web nuget 0.4.0-preview packages

startup.cs

public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
                    .AddMicrosoftIdentityWebApp(Configuration, "AzureAd")
                        .EnableTokenAcquisitionToCallDownstreamApi()
                            .AddMicrosoftGraph(Configuration.GetSection("GraphBeta"))
                            .AddDownstreamWebApi("CalledApi", Configuration.GetSection("CalledApi"))                            
                        .AddInMemoryTokenCaches();

            //---------------------------------------                        
            services.AddTokenAcquisition(false);
            //---------------------------------------

            services.AddControllersWithViews()
                    .AddMicrosoftIdentityUI();

            services.AddAuthorization(options =>
            {
                // By default, all incoming requests will be authorized according to the default policy
                options.FallbackPolicy = options.DefaultPolicy;
            });

            services.AddRazorPages();
            services.AddServerSideBlazor()
                       .AddMicrosoftIdentityConsentHandler();
            
            //---------------------------------------
            services.AddSignalR().AddAzureSignalR();
            //---------------------------------------

            services.AddSingleton<WeatherForecastService>();
        }

The project that reproduces the error is based on the "BlazorServerCallsGraph" test included with Microsoft.Identity.Web:
https://github.com/jassent/Example-Blazor-Graph-Microsoft-Identity-Web/tree/master/tests/BlazorServerCallsGraph

@jassent
Copy link
Author

jassent commented Sep 14, 2020

@jennyf19, after adding services.AddTokenAcquisition(false); there is no looping redirect. Instead there is this error: "InvalidOperationException: Cannot resolve scoped service 'Microsoft.Identity.Web.ITokenAcquisition' from root provider."

@jennyf19 jennyf19 self-assigned this Sep 15, 2020
@jennyf19
Copy link
Collaborator

@jassent i am able to repro the issue, will hopefully have an update for you this week. thanks.

@jassent
Copy link
Author

jassent commented Sep 17, 2020

Good luck! You got this!

@jennyf19
Copy link
Collaborator

@jassent do you want to try this branch? making the token acquisition a scoped service seems to be the fix. Blazor cannot handle singletons.

@jennyf19 jennyf19 added bug Something isn't working P1 labels Sep 18, 2020
@jennyf19 jennyf19 added this to the GA version milestone Sep 18, 2020
@jennyf19 jennyf19 added the duplicate This issue or pull request already exists label Sep 19, 2020
@jassent
Copy link
Author

jassent commented Sep 20, 2020

@jassent do you want to try this branch? making the token acquisition a scoped service seems to be the fix. Blazor cannot handle singletons.

@jennyf19 this branch worked! No more looping redirect! The calls to graph return data w/ Azure Signalr Server wired-up. Thank you for the attention, quick turnaround and solution. Would you like me to "close with comment" or is that something you will do?

@jennyf19
Copy link
Collaborator

@jassent awesome! Thanks for getting back to us. We'll leave it open until the release w/this fix happens, probably end of month. Thanks again.

@jmprieur
Copy link
Collaborator

@jassent, the fix is available in Microsoft.Identity.Web 1.0.0

@jassent
Copy link
Author

jassent commented Oct 1, 2020

@jmprieur & @jennyf19, thank you! Will give it a try in the next few days. I appreciate the work and the follow-up communication.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Azure SignalR Service Blazor bug Something isn't working duplicate This issue or pull request already exists fixed P1
Projects
None yet
Development

No branches or pull requests

3 participants