简体   繁体   中英

How to sign in a User in blazor server in a service layer?

I'm asking you this question today because I'm stuck since a week with this problem. Actually, I want to use a Service Layer Class to process the login and register of a user in my website. The register part works well, but the Login part doesn't because I receive the following error:

Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer: Warning: Unhandled exception rendering component: Headers are read-only, response has already started.

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, AuthenticationPrope rties 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 Rpg_Agenda.Services.LoginService.LoginService`1.Login(LoginModel logInInfos) in C:\Users\cleme\source\repos\Rpg_Agenda\Rpg_Agenda\Services\LoginService\LoginService.cs:line 41 at Rpg_Agenda.Pages.Account.Login.LoginClicked() in C:\Users\cleme\source\repos\Rpg_Agenda\Rpg_Agenda\Pages\Account\Login.razor:line 42 at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at MudBlazor.MudBaseButton.OnClickHandler(MouseEventArgs ev) at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState) Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost: Error: Unhandled exception in circuit '2psGTBryvsuncvK6qlYQl3f4w_cpcOsDaescmCRQRRA'.

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, AuthenticationPrope rties 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 Rpg_Agenda.Services.LoginService.LoginService`1.Login(LoginModel logInInfos) in C:\Users\cleme\source\repos\Rpg_Agenda\Rpg_Agenda\Services\LoginService\LoginService.cs:line 41 at Rpg_Agenda.Pages.Account.Login.LoginClicked() in C:\Users\cleme\source\repos\Rpg_Agenda\Rpg_Agenda\Pages\Account\Login.razor:line 42 at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at MudBlazor.MudBaseButton.OnClickHandler(MouseEventArgs ev) at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

At the line: SignInResult signInResult = await _signInManager.PasswordSignInAsync(user, logInInfos.Password, logInInfos.RememberMe, lockoutOnFailure: false);

I have the same error if I call the PasswordSignInAsync with the user Email instead of the User Object.

I tried before with a Middleware layer Login and it works, but I don't really like it in this usecase since the Invoke is called everytime when I need it from time to time.

Here is my code:

LoginService.cs:

public class LoginService<TUser> where TUser : class
{
    private readonly SignInManager<TUser> _signInManager;
    private readonly UserManager<TUser> _userManager;

    public LoginService(SignInManager<TUser> signInManager, UserManager<TUser> userManager)
    {
        _signInManager = signInManager;
        _userManager = userManager;
    }

    public async Task<bool> Login(LoginModel logInInfos)
    {
        TUser user = await _userManager.FindByEmailAsync(logInInfos.Email);
        if (user == null)
        {
            logInInfos.Error = ServiceErrors.LOGIN_WRONG_CREDENTIALS;
            return false;
        }

        if (!await _signInManager.CanSignInAsync(user))
        {
            logInInfos.Error = ServiceErrors.LOGIN_ACCOUNT_NOT_ACTIVE;
            return false;
        }

        if (!(await _signInManager.CheckPasswordSignInAsync(user, logInInfos.Password, true)).Succeeded)
        {
            logInInfos.Error = ServiceErrors.LOGIN_WRONG_CREDENTIALS;
            return false;
        }

        SignInResult signInResult = await _signInManager.PasswordSignInAsync(user, logInInfos.Password, logInInfos.RememberMe, lockoutOnFailure: false);
        if (signInResult.IsLockedOut)
        {
            logInInfos.Error = ServiceErrors.LOGIN_LOCKED_OUT;
            return false;
        }
        else if (signInResult.IsNotAllowed)
        {
            logInInfos.Error = ServiceErrors.LOGIN_NOT_ALLOWED;
            return false;
        }
        else if (!signInResult.Succeeded)
        {
            logInInfos.Error = ServiceErrors.LOGIN_ERROR;
            return false;
        }
        logInInfos.Password = null;
        return true;
    }
}

Login.razor:

@page "/login"
@using Microsoft.AspNetCore.Identity
@using RpgAgenda.Data.Entities
@using Rpg_Agenda.Shared
@using Rpg_Agenda.Services.LoginService
@using Rpg_Agenda.Services.LoginService.Models

@inject LoginService<User> loginService
@inject NavigationManager NavMgr

<MudGrid Justify="Justify.Center">
    <MudItem xs="4">
        <MudPaper Class="pa-4" Elevation="3">
            <MudForm @ref="form">
                <ErrorField errorValue="@loginModel.Error"></ErrorField>

                <MudTextField T="string" Label="Email" Required="true" @ref="email" RequiredError="Email is required" />
                <MudTextField T="string" InputType="InputType.Password" Label="Password" Required="true" @ref="password" RequiredError="Password is required" />

                <div class="d-flex align-center justify-center mt-6">
                    <MudButton FullWidth="true" OnClick="LoginClicked" Variant="Variant.Filled" Color="Color.Primary">Login</MudButton>
                </div>
            </MudForm>
        </MudPaper>
    </MudItem>
</MudGrid>

@code {
    private MudTextField<string> email;
    private MudTextField<string> password;
    private MudForm form;
    private LoginModel loginModel = new LoginModel();

    private async Task LoginClicked()
    {
        await form.Validate();

        if (form.IsValid)
        {
            loginModel.Email = email.Value;
            loginModel.Password = password.Value;
            bool isLoggedIn = await loginService.Login(loginModel);
            if(isLoggedIn)
            {
                NavMgr.NavigateTo("/");
            }
        }
    }
}

Startup.cs:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddMudServices();

        services.AddDbContext<Rpg_AgendaContext>(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));

        services.AddDefaultIdentity<User>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<Rpg_AgendaContext>();

        services.AddScoped<SignInManager<User>>();
        services.AddScoped<UserManager<User>>();

        services.AddScoped<LoginService<User>>();

        services.AddScoped<RegisterService<User>>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapBlazorHub();
            endpoints.MapFallbackToPage("/_Host");
        });
    }
}

Thank you in advance for helping me.

Your question can best be answered with another-- why doesn't Blazor ALREADY have the capacity to arbitrarily and easily log in? Why does it use MVC-style membership pages (scaffolded pages etc?), which many people find annoying and inconvenient?

I think you'll find that html requests just don't work the way you want them to. Once the request passes middleware, you can't change the user claims.

Middleware can intercept the full request, including posted password data, so I recommend using a custom URL for logging in. You can catch the URL, log the client in, and then navigate to the previous page.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM