问题
I am building a sample login razor component for an Asp.net core 3.0 Blazor Server-Side app. Whenever the code reaches the SignInAsyc method it just appears to hang or lock-up, as the code ceases further execution. I also tried switching up the logic, by using the PasswordSignInAsync method which gave me the exact same result. All code would execute before that method, but then freeze upon execution of that statement. What am I missing here?
Razor component page:
<div class="text-center">
<Login FieldsetAttr="fieldsetAttr" UsernameAttr="usernameAttr" PasswordAttr="passwordInput"
ButtonAttr="buttonAttr" ButtonText="Sign In" InvalidAttr="invalidAttr" />
</div>
@code {
Dictionary<string, object> fieldsetAttr =
new Dictionary<string, object>()
{
{"class", "form-group" }
};
Dictionary<string, object> usernameAttr =
new Dictionary<string, object>()
{
{"class", "form-control" },
{"type", "text" },
{"placeholder", "Enter your user name here." }
};
Dictionary<string, object> passwordInput =
new Dictionary<string, object>()
{
{"class", "form-control" },
{"type", "password" }
};
Dictionary<string, object> buttonAttr =
new Dictionary<string, object>()
{
{"type", "button" }
};
Dictionary<string, object> invalidAttr =
new Dictionary<string, object>()
{
{"class", "" },
{"style", "color: red;" }
};
Dictionary<string, object> validAttr =
new Dictionary<string, object>()
{
{"class", "" },
{"style", "color: green;" }
};
}
Razor component:
@inject SignInManager<IdentityUser> signInManager
@inject UserManager<IdentityUser> userManager
<div @attributes="FormParentAttr">
<form @attributes="LoginFormAttr">
<fieldset @attributes="FieldsetAttr">
<legend>Login</legend>
<label for="usernameId">Username</label><br />
<input @attributes="UsernameAttr" id="usernameId" @bind="UserName" /><br />
<label for="upasswordId">Password</label><br />
<input @attributes="PasswordAttr" id="passwordId" @bind="Password" /><br />
<button @attributes="ButtonAttr" @onclick="@(async e => await LoginUser())">@ButtonText</button>
@if (errorMessage != null && errorMessage.Length > 0)
{
<div @attributes="InvalidAttr">
@errorMessage
</div>
}
else if(successMessage != null && successMessage.Length > 0)
{
<div @attributes="ValidAttr">
@successMessage
</div>
}
</fieldset>
</form>
</div>
@code {
string successMessage = "";
private async Task LoginUser()
{
if(!String.IsNullOrEmpty(UserName))
{
var user = await userManager.FindByNameAsync(UserName);
var loginResult =
await signInManager.CheckPasswordSignInAsync(user, Password, false);
if(loginResult.Succeeded)
{
await signInManager.SignInAsync(user, true);
successMessage = $"{UserName}, signed in.";
errorMessage = "";
}
else
{
successMessage = "";
errorMessage = "Username or password is incorrect.";
}
}
else
{
successMessage = "";
errorMessage = "Provide a username.";
}
}
[Parameter]
public Dictionary<string, object> FormParentAttr { get; set; }
[Parameter]
public Dictionary<string, object> LoginFormAttr { get; set; }
[Parameter]
public Dictionary<string, object> FieldsetAttr { get; set; }
[Parameter]
public Dictionary<string, object> UsernameAttr { get; set; }
[Parameter]
public Dictionary<string, object> PasswordAttr { get; set; }
[Parameter]
public Dictionary<string,object> ButtonAttr { get; set; }
[Parameter]
public Dictionary<string, object> InvalidAttr { get; set; }
private string UserName { get; set; }
private string Password { get; set; }
[Parameter]
public string ButtonText { get; set; }
[Parameter]
public Dictionary<string, object> ValidAttr { get;set; }
public string errorMessage { get; set; }
}
回答1:
Basically, it happens because the SigninManger::SignInAsync() will actually try to send a cookie over HTTP to indicate this user has already signed in. But when dealing with Blazor Server Side at this moment, there's no available HTTP Response at all , there's only a WebSocket connection (SignalR).
How to Fix
In a nutshell, Signin is to persist user credentials/cookies/... so that the WebApp knows who the client is. Since you're using a Blazor Server Side, your client is talking to the server within a WebSocket connection. There's no need to send cookies over HTTP. Because your WebApp has already knows who the current user is.
To fix this issue, register an IHostEnvironmentAuthenticationStateProvider service firstly:
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddScoped<IHostEnvironmentAuthenticationStateProvider>(sp => {
// this is safe because
// the `RevalidatingIdentityAuthenticationStateProvider` extends the `ServerAuthenticationStateProvider`
var provider = (ServerAuthenticationStateProvider) sp.GetRequiredService<AuthenticationStateProvider>();
return provider;
});
And then create a principal and replace the old one .
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject IHostEnvironmentAuthenticationStateProvider HostAuthentication
...
var user = await userManager.FindByNameAsync(UserName);
var valid= await signInManager.UserManager.CheckPasswordAsync(user, Password);
if (valid)
{
var principal = await signInManager.CreateUserPrincipalAsync(user);
var identity = new ClaimsIdentity(
principal.Claims,
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme
);
principal = new System.Security.Claims.ClaimsPrincipal(identity);
signInManager.Context.User = principal;
HostAuthentication.SetAuthenticationState(Task.FromResult(new AuthenticationState(principal)));
// now the authState is updated
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
successMessage = $"{UserName}, signed in.";
errorMessage = "";
}
else
{
successMessage = "";
errorMessage = "Username or password is incorrect.";
}
Demo
And check the authState:
来源:https://stackoverflow.com/questions/58387683/signin-for-blazor-server-side-app-not-working