Entity Framework avec page de connexion Blazor
Lorsqu'il s'agit d'utiliser Identity Framework avec Blazor Server, la déclaration officielle de Microsoft est qu'il n'est pas pris en charge et d'utiliser une page de connexion Razor à la place. Le problème est que Blazor ne peut pas écrire de cookies.
Cela présente quelques inconvénients:
- Impossible de réutiliser les fichiers de mise en page HTML, besoin de recréer des fichiers de mise en page en double pour Razor.
- Impossible d'intégrer les boutons de connexion sur la page Blazor
- Mauvaise expérience utilisateur si vous devez éventuellement vous connecter dans le cadre d'une expérience de paiement
Ce type a trouvé un moyen de faire fonctionner une page de connexion Blazor avec Blazor WebAssembly ... Je ne sais pas comment il a résolu le problème ni si une approche similaire peut fonctionner pour Blazor Server.
Je pensais à des solutions alternatives. Le problème est de stocker le cookie. Le stockage local peut être utilisé dans Blazor, mais le stockage local n'est pas sûr pour les jetons de sécurité. Cependant, les cookies peuvent également être définis via l'interopérabilité JavaScript de la même manière.
Cette approche fonctionnerait-elle, en définissant le cookie de connexion via l'interopérabilité JavaScript après la connexion, puis le cookie serait envoyé à tout autre chargement de page? Quelqu'un l'a-t-il fait?
Pour le contenu administrateur d'une seule page, j'ai trouvé une approche plus simple de création d'un composant GatedContent qui affiche un formulaire de connexion, puis affiche ChildContent après la connexion. Bien sûr, cela ne conservera pas la session lors de l'actualisation de la page, mais cela fonctionne dans certains cas.
Réponses
Voici une solution pour le contenu administrateur d'une seule page: GatedContent
Il affichera un formulaire de connexion, et après une connexion réussie, affichera le contenu fermé.
SpinnerButton est défini ici.
@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;
}
}
}
Utilisez comme ça
<GatedContent>
This is an admin page.
</GatedContent>
Bien sûr, cette solution ne conserve pas l'état après le rechargement de la page, mais elle fonctionne pour les pages d'administration simples.
Ma solution de contournement est la suivante: j'ai créé un SignInController comme celui-ci
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("/");
}
}
Et j'ai un login.razor comme celui-ci:
@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>
Ce problème est causé par le signInManager qui ne fonctionne que si vous utilisez des requêtes HTTP / S. Sinon, cela lancera des exceptions. Il existe également des rapports à ce sujet sur le repo dotnet / aspcore.net sur GitHub.
Pour le moment, c'est le seul moyen (je sais) de l'utiliser avec le serveur blazor.