Entity Framework com página de login do Blazor
Quando se trata de usar o Identity Framework com Blazor Server, a declaração oficial da Microsoft é que ele não é compatível e, em vez disso, use uma página de login do Razor. O problema é que o Blazor não consegue escrever cookies.
Isso tem algumas desvantagens:
- Não é possível reutilizar os arquivos de layout HTML, é necessário recriar arquivos de layout duplicados para o Razor.
- Não é possível incorporar botões de login na página do Blazor
- Má experiência do usuário se for necessário fazer o login opcionalmente como parte de uma experiência de checkout
Esse cara descobriu uma maneira de fazer uma página de login do Blazor funcionar com o Blazor WebAssembly ... não tenho certeza de como ele contornou o problema nem se uma abordagem semelhante pode funcionar para o Blazor Server.
Eu estava pensando em soluções alternativas. O problema é armazenar o cookie. O armazenamento local pode ser usado no Blazor, mas o armazenamento local não é seguro para tokens de segurança. No entanto, os cookies também podem ser configurados por meio da interoperabilidade de JavaScript de maneira semelhante.
Essa abordagem funcionaria, configurando o cookie de login via interoperabilidade JavaScript após o login e, em seguida, o cookie seria enviado em qualquer carregamento de página posterior? Alguém já fez isso?
Para conteúdo administrativo de página única, descobri uma abordagem mais simples de criar um componente GatedContent que mostra um formulário de login e, em seguida, mostra ChildContent após o login. Claro, isso não preservará a sessão na atualização da página, mas funciona para alguns casos.
Respostas
Esta é uma solução para conteúdo administrativo de página única: GatedContent
Ele mostrará um formulário de login e, após o login bem-sucedido, mostrará o conteúdo bloqueado.
SpinnerButton é definido aqui.
@using Microsoft.AspNetCore.Identity
@using System.ComponentModel.DataAnnotations
@inject NavigationManager navManager
@inject SignInManager<ApplicationUser> signInManager
@inject UserManager<ApplicationUser> userManager;
@if (!LoggedIn)
{
<EditForm Context="formContext" class="form-signin" OnValidSubmit="@SubmitAsync" Model="this">
<DataAnnotationsValidator />
<div style="margin-bottom: 1rem; margin-top: 1rem;" class="row">
<Field For="@(() => Username)" Width="4" Required="true">
<RadzenTextBox @bind-Value="@Username" class="form-control" Style="margin-bottom: 0px;" />
</Field>
</div>
<div style="margin-bottom: 1rem" class="row">
<Field For="@(() => Password)" Width="4" Required="true">
<RadzenPassword @bind-Value="@Password" class="form-control" />
</Field>
</div>
<SpinnerButton @ref="ButtonRef" style="width:150px" Text="Login" ButtonType="@((Radzen.ButtonType)ButtonType.Submit)" ButtonStyle="@((Radzen.ButtonStyle)ButtonStyle.Primary)" OnSubmit="LogInAsync" />
@if (Error.HasValue())
{
<div class="red">@Error</div>
}
</EditForm>
}
else
{
@ChildContent
}
@code {
public SpinnerButton? ButtonRef { get; set; }
public async Task SubmitAsync() => await ButtonRef!.FormSubmitAsync().ConfigureAwait(false);
[Required]
public string Username { get; set; } = string.Empty;
[Required]
public string Password { get; set; } = string.Empty;
string? Error { get; set; }
bool LoggedIn { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
public async Task LogInAsync()
{
Error = null;
try
{
var user = await userManager.FindByNameAsync(Username).ConfigureAwait(false);
if (user != null)
{
var isAdmin = await userManager.IsInRoleAsync(user, ApplicationRole.Admin).ConfigureAwait(false);
if (isAdmin)
{
var singInResult = await signInManager.CheckPasswordSignInAsync(user, Password, true).ConfigureAwait(false);
if (singInResult.Succeeded)
{
LoggedIn = true;
}
else
{
Error = "Invalid password";
}
}
else
{
Error = "User is not Admin";
}
}
else
{
Error = "Username not found";
}
}
catch (Exception ex)
{
Error = ex.Message;
}
}
}
Usar assim
<GatedContent>
This is an admin page.
</GatedContent>
Obviamente, essa solução não preserva o estado após o recarregamento da página, mas funciona para páginas de administração simples.
Minha solução alternativa é esta: Eu criei um SignInController como este
public class SignInController : ControllerBase
{
private readonly SignInManager<IdentityUser> _signInManager;
public SignInController(SignInManager<IdentityUser> signInManager)
{
_signInManager = signInManager;
}
[HttpPost("/signin")]
public IActionResult Index([FromForm]string username, [FromForm]string password, [FromForm]string rememberMe)
{
bool.TryParse(rememberMe, out bool res);
var signInResult = _signInManager.PasswordSignInAsync(username, password, res, false);
if (signInResult.Result.Succeeded)
{
return Redirect("/");
}
return Redirect("/login/"+ signInResult.Result.Succeeded);
}
[HttpPost("/signout")]
public async Task<IActionResult> Logout()
{
if (_signInManager.IsSignedIn(User))
{
await _signInManager.SignOutAsync();
}
return Redirect("/");
}
}
E eu tenho um login.razor como este:
@page "/login"
<form action="/signin" method="post">
<div class="form-group">
<p> Username </p>
<input id="username" Name="username" />
</div>
<div class="form-group">
<p>password<p/>
<input type="password" id="password" />
</div>
<div class="form-group">
<p> remember me? <p/>
<input type="checkbox" id="rememberMe" />
</div>
<button type="Submit" Text="login" />
</form>
Esse problema é causado pelo signInManager, que só funciona se você usar solicitações HTTP / S. Caso contrário, ele lançará exceções. Existem também alguns relatórios sobre isso no repositório dotnet / aspcore.net no GitHub.
No momento, esta é a única maneira (eu sei) de usá-lo com o servidor blazor.