dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.21k stars 9.95k forks source link

Blazor Api call in OnInitializedAsync() with auth and UserMiddleware crash #52350

Closed fdonnet closed 10 months ago

fdonnet commented 10 months ago

Is there an existing issue for this?

Describe the bug

When I call an api from a blazor page I think my middleware crash and I don't understand why


<button class="btn btn-primary" @onclick="SayHello">SayHello</button>
<p role="status">Say: @say</p>

@code {
    [CascadingParameter]
    private Task<AuthenticationState>? authenticationState { get; set; }
    private string say = string.Empty;

    private async Task SayHello()
    {
        var client = Factory.CreateClient("WebApp");
        say = await client.GetStringAsync("/Hello");
    }

    protected override async Task OnInitializedAsync()
    {
        if (authenticationState is not null)
        {
            var isAuthenticated = (await authenticationState).User.Identity?.IsAuthenticated;

            if (isAuthenticated == true)
            {
                var client = Factory.CreateClient("WebApp");
                var test = await client.GetStringAsync("/Hello");
            }
        }

    }
}

If the api call is called inside OnInitializedAsync it doesn't work, when it's trigger by the Hello button outside of OnInitializedAsync it works.

I wait the authenticatioState correctly I think.

It crash server side in my user middleware, when I set my user

   public class UserServiceMiddleware(RequestDelegate next)
    {
        private readonly RequestDelegate next = next ?? throw new ArgumentNullException(nameof(next));

        public async Task InvokeAsync(HttpContext context, UserService service)
        {
            service.SetUser(context.User);
            await next(context);
        }
    }

(like explained here https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/additional-scenarios?view=aspnetcore-8.0) to be able to inject my user in other services server side.

Very strange.

Expected Behavior

Same behavior as calling the API with a button click... I'dont understand why my middleware creates this issue for OnInitializedAsync. It crash on await next(context);

Steps To Reproduce

No response

Exceptions (if any)

it returns

HttpRequestException: Response status code does not indicate success: 302 (Found).

    System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
    System.Net.Http.HttpClient.GetStringAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
    Ubik.Accounting.WebApp.Client.Pages.Auth.OnInitializedAsync() in Auth.razor

                    var test = await client.GetStringAsync("/Hello");

Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.WaitForResultReady(bool waitForQuiescence, PrerenderedComponentHtmlContent result)
Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.RenderEndpointComponent(HttpContext httpContext, Type rootComponentType, ParameterView parameters, bool waitForQuiescence)
System.Threading.Tasks.ValueTask<TResult>.get_Result()
Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore(HttpContext context)
Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore(HttpContext context)
Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext+<>c+<<InvokeAsync>b__10_0>d.MoveNext()
Ubik.Accounting.WebApp.Security.UserServiceMiddleware.InvokeAsync(HttpContext context, UserService service) in UserServiceMiddleware.cs

                await next(context);

Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

.NET Version

8.0

Anything else?

No response

fdonnet commented 10 months ago

This guy bcheung4589 helped me a lot with the explanation here https://github.com/dotnet/aspnetcore/issues/51468 to manage auto mode... that's why I had my issue and his facade proposition works for me... now that my page is called from server first and from wasm after, I only need to understand why my initial data load is cleaned after the first render

bcheung4589 commented 10 months ago

I have a direct answer for you, as Ive experienced this personally.

Problem: on the second run, UserServiceMiddleware isnt executed and so UserService stays empty => with all its further consequences.

Solution: what to do? Refactor UserService with RequireUserAsync(ClaimsPrincipal user).

Implementation: in the razor component (yes, Im also not 100% satisfied with this solution, but it works for now):

    protected override async Task OnInitializedAsync()
    {
        // Require User or move away.
        await UserService.RequireUserAsync(AuthenticationStateProvider, Navigation);

        // Run as usual.
        await base.OnInitializedAsync();
    }