sotsera / sotsera.blazor.oidc

OpenID Connect client for Blazor client-side projects
https://blazor-oidc.sotsera.com/
Apache License 2.0
20 stars 8 forks source link

Using the lib with server-side pre-rendering fails #18

Open ChristianWeyer opened 4 years ago

ChristianWeyer commented 4 years ago

Hi :-)

I am trying to use your awesome lib together with server-side pre-rendering. When doing so I get this exception:

System.InvalidOperationException: 'HttpNavigationManager' has not been initialized.
   at Microsoft.AspNetCore.Components.NavigationManager.AssertInitialized()
   at Microsoft.AspNetCore.Components.NavigationManager.get_BaseUri()
   at Microsoft.Extensions.DependencyInjection.IServiceCollectionExtensions.<>c__DisplayClass0_0.<AddOidc>b__0(IServiceProvider b)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Sotsera.Blazor.Oidc.Core.UserManager..ctor(IServiceProvider serviceProvider)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.InitializeStandardComponentServices(HttpContext httpContext)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.PrerenderComponentAsync(ParameterView parameters, HttpContext httpContext, Type componentType)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.StaticComponentAsync(HttpContext context, Type type, ParameterView parametersCollection)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.RenderComponentAsync(ViewContext viewContext, Type componentType, RenderMode renderMode, Object parameters)
   at BlazorConfTool.Server.Pages.Pages__Host.<ExecuteAsync>b__8_1() in C:\sources\basta-spring-2020-blazor-workshop\BlazorConfTool\BlazorConfTool\Server\Pages\_Host.cshtml:line 33
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
   at BlazorConfTool.Server.Pages.Pages__Host.ExecuteAsync()
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Builder.BlazorMonoDebugProxyAppBuilderExtensions.<>c.<<UseVisualStudioDebuggerConnectionRequestHandlers>b__5_0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

I think this is related to this issue: https://github.com/dotnet/aspnetcore/issues/11867

What do you think about adopting the proposed solution in the above issue?

Thanks!

ghidello commented 4 years ago

Ciao! :) Thanks a lot for investigating on this. I tried to implement the workaround suggested by Steve but I haven't tested the server-side pre-rendering. Actually I'm quite interested in it: do you have any sample to share? Anyway I published a new library version. Let me know if you have a chance to test it.

ChristianWeyer commented 4 years ago

Hi!

I just tried the latest build, and it did not change things.

Here is my demo application: https://github.com/thinktecture/basta-spring-2020-blazor/tree/ssr

Thanks!

ghidello commented 4 years ago

Hi, thanks for your sample. I tried to add the oidc library directly into it and I found that I had to apply the workaround to all the others UserManager dependencies. Now it breaks because of a disposed IServiceProvider instance. To be honest, following Brock Allen suggestion to relay on cookies when possible, I developed the library focusing only on WebAssembly stand alone. I would have to investigate more on this issue, the services scope and the browser dependencies (like the session storage). I'll see if I'll can play around it during the weekend.

ChristianWeyer commented 4 years ago

Thanks! Yeah, it seems like some effort just to get the initial pre-rendering done.

sabitertan commented 4 years ago

How about early initialization of userManager? As of 3.2preview startup model changed, this may fall into that feature? https://docs.microsoft.com/en-us/aspnet/core/blazor/dependency-injection?view=aspnetcore-3.1#add-services-to-an-app I like your library, but I think need some structural changes to support different use cases. I created different project based on oidc-client.js and used an initialization strategy idea that I saw in Blazor.Fluxor. Hope that can give some sparks. https://github.com/sabitertan/BlazorAspnetHostedOidcJS/blob/master/Shared/UserManagerInteropStrategy.cs

ghidello commented 4 years ago

Hi @ChristianWeyer and sorry for the late reply. I reviewed a bit better the issue that you linked and the HttpClient problem was just the first one throwing an exception. The library, as it is designed, relies on the browser storage and an iframe for the identity server session monitoring that are obviously not present server side. These resources are accessed by the custom AuthenticationStateProvider that is used by the AuthorizeRouteView and the Authorize attributes and I don't think that their actions can be postponed to when the application has been rendered to the client.

I found the new libraries that hopefully will handle the wasm autentication out of the box. One of them is for MSAL and one is using oidc-client: I'm planning to study them more in the next days but they look to be a client only solution with no server prerender option or at least I couldn't spot it immediately.

In the end, right now I'm thinking that prerendering is not a viable option for this library. I hope to be proven wrong though 😄

ghidello commented 4 years ago

Hi @sabitertan and thanks for your links. I will definitely look a them. From a quick scan of your repo, though, I couldn't find an AuthenticationStateProvider implementation so I don't know how you're planning to integrate with the other authorization components but I would definitely need some guidance on that.

sabitertan commented 4 years ago

Hi @ghidello , I think we should wait Blazor 3.2 Preview2 as I checked dotnet repo there is a new Authentication template for Blazor-wasm. And I was thinking provide 2 different AuthenticationStateProvider depends on if we are on wasm or server. Either way I think dotnet team will release 3.2Preview2 next week :)

sabitertan commented 4 years ago

Here is the proposed documentation: https://gist.github.com/javiercn/85a6d4ce46a78239ccb427baad3a66da

ChristianWeyer commented 4 years ago

How did you find that @sabitertan ? :-)

sabitertan commented 4 years ago

@ChristianWeyer I was following their board :D. https://github.com/dotnet/aspnetcore/projects/9