Search code examples
c#asp.netrazorasp.net-identityblazor

Blazor Server: Custom Login Page


I'm working on a Blazor Server project to create custom login and registration pages. The page below is a testing page for custom login. If a correct username is used, the user object is successfully obtained. However, it throws error when signing in at the code await _signInManager.SignInAsync(user, false);. The error message is

System.InvalidOperationException: 'Headers are read-only, response has already started.'.

How to solve this issue?

@page "/"
@using Microsoft.AspNetCore.Identity
@using Blazor.PMC.Models.Identity
@inject UserManager<ApplicationUser> _userManager
@inject SignInManager<ApplicationUser> _signInManager

<AuthorizeView>
    <Authorized>
        <h1>Hello @context.User.Identity.Name !!</h1>
        <p>Welcome to your new app.</p>
    </Authorized>
    <NotAuthorized>
        <div class="form-group row m-b-15">
            <label class="col-md-3 col-form-label">Username</label>
            <div class="col-md-7">
                <input @bind-value="Username" type="text" class="form-control" placeholder=" Enter username" />
            </div>
        </div>
        <div class="form-group row m-b-15">
            <label class="col-md-3 col-form-label">Password</label>
            <div class="col-md-7">
                <input @bind-value="Password" type="text" class="form-control" placeholder="Enter password" />
            </div>
        </div>
        <button type="submit" @onclick="LoginUser" class="btn btn-sm btn-primary m-r-5">Login</button>
    </NotAuthorized>
</AuthorizeView>

<br />

@Message

@code {
    string Username;
    string Password;
    string Message;

    private async void LoginUser()
    {
        var user = await _userManager.FindByNameAsync(Username);
        if (user == null)
            Message = "User does not exist";

        var singInResult = await _signInManager.CheckPasswordSignInAsync(user, Password, false);
        if (!singInResult.Succeeded)
            Message = "Invalid password";
        else
            await _signInManager.SignInAsync(user, false); // Error occurs here

        await InvokeAsync(() => StateHasChanged())
                    .ConfigureAwait(false);
    }
}

Solution

  • SignInManager and UserManager aren't supported in Razor components. Source...

    <button type="submit" @onclick="LoginUser" class="btn btn-sm btn-primary m-r- 
      5">Login</button>
    

    You shouldn't use "submit" button type if you call the local method LoginUser. Note also that the "submit" button is always embedded within a form element. I think that in either case, the flow of execution goes out of your Blazor SPA space, and it shouldn't in this case.

    Is there a good reason why you try to implement authentication on your own ? It's very hard, and your code may have many bugs. Why not use existing systems.

    Note: If you still want to do that yourself, you can gather data from your user, and pass it to a Web Api (Create a Web Api project) end points, where you have access to the SignInManager and UserManager objects and more. You'll have to use the HttpClient service for communication between your Blazor App and your Web Api end-points. You may also use Jwt token authentication and authorization in a similar fashion.