Skip to content

Updating UI in the scaffolded authentication pages is not working #61611

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

Open
1 task done
emimontesdeoca opened this issue Apr 22, 2025 · 3 comments
Open
1 task done
Labels
area-identity Includes: Identity and providers Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update.

Comments

@emimontesdeoca
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Hey!

I might be using this wrongly but I have not found any kind of documentation about this, which I think is related to the SSR/StreamRendering not being enabled for this view.

So far the issue for me is that I have my login page which I want to update while performing the login process, basically just standard spinner in the button, nothing fancy.

The code is the following:

@page "/auth/login"
@using System.ComponentModel.DataAnnotations
@using CallForPapersAI.Applications.Portal.Managers
@using CallForPapersAI.Libraries.Entities
@using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Identity

@inject SignInManager<ApplicationUser> SignInManager
@inject ILogger<Login> Logger
@inject NavigationManager NavigationManager
@inject IdentityRedirectManager RedirectManager


<PageTitle>Log in</PageTitle>

<div class="card card-md">
    <div class="card-body">
        <h2 class="h2 text-center mb-4">Login to your account</h2>
        <EditForm Model="Input" method="post" OnValidSubmit="LoginUser" FormName="login" autocomplete="off" >

            @if (!string.IsNullOrEmpty(errorMessage))
            {
                <p class="text-danger" role="alert">
                    @errorMessage
                </p>
            }

            <div class="mb-3">
                <label for="email" class="form-label">Email</label>
                <InputText @bind-Value="Input.Email" class="form-control" aria-required="true" placeholder="john@doe.com" />
                <ValidationMessage For="() => Input.Email" class="text-danger" />
            </div>
            <div class="mb-2">
                <label class="form-label">
                    Password
                    <span class="form-label-description">
                        <a href="/auth/forgot-password">I forgot password</a>
                    </span>
                </label>
                <div class="input-group input-group-flat">
                    <InputText type="password" @bind-Value="Input.Password" class="form-control" aria-required="true" placeholder="password" />
                    <ValidationMessage For="() => Input.Password" class="text-danger" />
                </div>
            </div>
            <div class="mb-2">
                <label class="form-check">
                    <InputCheckbox @bind-Value="Input.RememberMe" class="form-check-input" />
                    <span class="form-check-label">Remember me on this device</span>
                </label>
            </div>
            <div class="form-footer">
                @if (IsLoading)
                {
                    <button class="btn w-100">
                        <span class="spinner-border spinner-border-sm me-2" role="status"></span>
                    </button>
                    
                }
                else
                {
                    
                <button type="submit" class="btn w-100">Sign in</button>
                }

            </div>
        </EditForm>
    </div>
</div>
<div class="text-center text-secondary mt-3">Don't have account yet? <a href="/auth/register" tabindex="-1">Sign up</a></div>



@code {

    private string? errorMessage;

    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

    [SupplyParameterFromForm]
    private InputModel Input { get; set; } = new();

    [SupplyParameterFromQuery]
    private string? ReturnUrl { get; set; }

    public bool IsLoading { get; set; } = false;

    protected override async Task OnInitializedAsync()
    {
        if (HttpMethods.IsGet(HttpContext.Request.Method))
        {
            // Clear the existing external cookie to ensure a clean login process
            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
        }
    }

    public async Task LoginUser()
    {   
        IsLoading = true;
        await InvokeAsync(StateHasChanged);

        var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            Logger.LogInformation("User logged in.");
            RedirectManager.RedirectTo(ReturnUrl);
        }
        else
        {
            errorMessage = "Error: Invalid login attempt.";
        }

        IsLoading = false;
        await InvokeAsync(StateHasChanged);
    }

    private sealed class InputModel
    {
        [Required]
        [EmailAddress]
        public string Email { get; set; } = "";

        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; } = "";

        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }
}

If you take a look at this piece:

 public async Task LoginUser()
    {   
        IsLoading = true;
        await InvokeAsync(StateHasChanged);

        var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            Logger.LogInformation("User logged in.");
            RedirectManager.RedirectTo(ReturnUrl);
        }
        else
        {
            errorMessage = "Error: Invalid login attempt.";
        }

        IsLoading = false;
        await InvokeAsync(StateHasChanged);
    }

Ideally this should just update the button and display either one of them but it's not doing anything.

AFAIK from reading everywhere, this seems to be intended, I'd like to know how could we update the login page so I can add some magic in the UI.

I've tried to set add StreamRendering but then the authentication is broken and get's the following error:

@attribute [StreamRendering]
System.InvalidOperationException: Headers are read-only, response has already started.
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_SetCookie(StringValues value)
   at Microsoft.AspNetCore.Http.ResponseCookies.Append(String key, String value, CookieOptions options)
   at Microsoft.AspNetCore.Authentication.Cookies.ChunkingCookieManager.AppendResponseCookie(HttpContext context, String key, String value, CookieOptions options)
   at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
   at Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, String scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
   at Microsoft.AspNetCore.Identity.SignInManager`1.SignInWithClaimsAsync(TUser user, AuthenticationProperties authenticationProperties, IEnumerable`1 additionalClaims)
   at Microsoft.AspNetCore.Identity.SignInManager`1.SignInOrTwoFactorAsync(TUser user, Boolean isPersistent, String loginProvider, Boolean bypassTwoFactor)
   at Microsoft.AspNetCore.Identity.SignInManager`1.PasswordSignInAsync(TUser user, String password, Boolean isPersistent, Boolean lockoutOnFailure)
   at Microsoft.AspNetCore.Identity.SignInManager`1.PasswordSignInAsync(String userName, String password, Boolean isPersistent, Boolean lockoutOnFailure)
   at CallForPapersAI.Applications.Portal.Pages.Unautorhized.Auth.Login.LoginUser()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.<WaitForQuiescence>g__ProcessAsynchronousWork|62_0()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.WaitForQuiescence()
   at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.SendStreamingUpdatesAsync(HttpContext httpContext, Task untilTaskCompleted, TextWriter writer)

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

9.0.300-preview.0.25177.5

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Apr 22, 2025
@javiercn javiercn added area-identity Includes: Identity and providers and removed area-blazor Includes: Blazor, Razor Components labels Apr 22, 2025
@MackinnonBuck
Copy link
Member

With streaming rendering enabled, you can't set headers after the response has started. SignInManager.SignInAsync() sets headers, which causes this exception to occur. So, this behavior is expected.

One thing you could try is turning off streaming rendering and adding a JavaScript listener for the form's onsubmit event that programmatically makes the spinner visible. The spinner should automatically remain visible until the login page re-renders (failed sign in) or the page at the return URL renders.

Could you please let us know if this solution fits your needs? Thanks!

@MackinnonBuck MackinnonBuck added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label May 5, 2025
@emimontesdeoca
Copy link
Author

Hey @MackinnonBuck, makes complete sense what you've said.

I'll take a look and try playing with it and get back to you!

Thanks.

@dotnet-policy-service dotnet-policy-service bot added Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. labels May 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-identity Includes: Identity and providers Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update.
Projects
None yet
Development

No branches or pull requests

3 participants