Entity Framework con pagina di accesso Blazor
Quando si tratta di utilizzare Identity Framework con Blazor Server, la dichiarazione ufficiale di Microsoft è che non è supportato e di utilizzare invece una pagina di accesso Razor. Il problema è che Blazor non può scrivere cookie.
Questo ha alcuni inconvenienti:
- Non è possibile riutilizzare i file di layout HTML, è necessario ricreare i file di layout duplicati per Razor.
- Impossibile incorporare i pulsanti di accesso nella pagina Blazor
- Esperienza utente scadente se è necessario accedere facoltativamente come parte di un'esperienza di pagamento
Questo ragazzo ha trovato un modo per far funzionare una pagina di accesso di Blazor con Blazor WebAssembly ... non è sicuro di come ha risolto il problema né se un approccio simile può funzionare per Blazor Server.
Stavo pensando a soluzioni alternative. Il problema è memorizzare il cookie. L'archiviazione locale può essere usata in Blazor ma l'archiviazione locale non è sicura per i token di sicurezza. I cookie tuttavia potrebbero anche essere impostati tramite l'interoperabilità JavaScript in modo simile.
Questo approccio funzionerebbe, impostando il cookie di accesso tramite l'interoperabilità JavaScript dopo l'accesso e quindi il cookie verrà inviato a ogni ulteriore caricamento della pagina? Qualcuno l'ha fatto?
Per i contenuti di amministrazione di una sola pagina, ho trovato un approccio più semplice per la creazione di un componente GatedContent che mostra un modulo di accesso e quindi mostra ChildContent dopo l'accesso. Ovviamente, questo non preserverà la sessione all'aggiornamento della pagina, ma in alcuni casi funziona.
Risposte
Ecco una soluzione per il contenuto amministrativo di una sola pagina: GatedContent
Mostrerà un modulo di accesso e, dopo il login riuscito, mostrerà il contenuto gated.
SpinnerButton è definito qui.
@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;
}
}
}
Usa in questo modo
<GatedContent>
This is an admin page.
</GatedContent>
Ovviamente, questa soluzione non preserva lo stato dopo il ricaricamento della pagina, ma funziona per semplici pagine di amministrazione.
La mia soluzione alternativa è questa: ho creato un SignInController come questo
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 ho un login.razor come questo:
@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>
Questo problema è causato dal signInManager che funziona solo se si utilizzano richieste HTTP / S. In caso contrario, genererà eccezioni. Ci sono anche alcuni rapporti al riguardo nel repository dotnet / aspcore.net su GitHub.
Al momento questo è l'unico modo (lo so) per usarlo con blazor server.