Authentification Blazor WebAssembly

Apprenez les bases de l’authentification Blazor WebAssembly, y compris le système d’authentification d’identité.
L’authentification dans Blazor WebAssembly peut être difficile si c’est la première fois que vous implémentez, en particulier en raison du nombre de classes et de composants que le cadre ajoute si vous choisissez le Comptes individuels Option lors de la configuration du projet. Pour cette raison, tout au long de cet article, j’expliquerai les concepts clés que vous devez comprendre pour commencer votre voyage. Commençons!
Configuration d’un projet Blazor WebAssembly avec des comptes individuels
Un excellent moyen de se renseigner sur le processus d’authentification dans Blazor WebAssembly est d’utiliser le Blazor Web App
modèle, sélection Authentication Type
comme Individual Accounts
, Interactive Render Mode
comme WebAssembly
et Interactivity location
comme Global
:
La configuration ci-dessus indique que l’application doit appliquer le rendu de WEBAssembly à tous les composants (en théorie, vous verrez plus tard que cela ne se produit pas exactement comme ça). Ce modèle nous aidera à comprendre comment l’authentification côté serveur peut être réalisée tout en persistant des informations du côté client.
Après avoir créé le projet, je recommande d’exécuter l’application pour voir la structure initiale du projet. Vous pouvez voir qu’en dehors des pages traditionnelles d’un projet de blazor, les pages Auth Required
, Register
et Login
ont été créés.
Commençons par essayer de créer un compte sur le Register
page. Lorsque vous entrez les données et en cliquant sur le bouton, vous verrez une erreur se produit. Cette erreur se produit parce que la première migration n’a pas été appliquée pour que l’application fonctionne. Nous pouvons exécuter la commande recommandée dans le Package Manager Console
ou la solution plus simple est de cliquer sur le bouton bleu qui dit Apply Migrations
:
Après avoir appliqué la migration, nous devons simplement actualiser la page pour voir que nous pouvons maintenant nous inscrire sans que cette erreur apparaisse:
Une fois que nous avons le projet d’exemple prêt, examinons de plus près son fonctionnement.
Examiner la méthode AddCascAdingAuthentication State
Examinons le Program.cs
Fichier dans le projet Server, qui nous permettra de découvrir différents concepts importants dans le monde de l’authentification avec ASP.NET Core Identity. La première ligne que nous devrions examiner est celle qui invoque le AddCascadingAuthenticationState
méthode:
builder.Services.AddCascadingAuthenticationState();
L’exécution de cette méthode est de la plus haute importance car en interne, il enregistrera les services nécessaires afin que l’état d’authentification de l’utilisateur soit disponible dans la hiérarchie des composants lorsque l’application est en cours d’exécution. Certains composants comme AuthorizeView
ne pouvait pas fonctionner si cette méthode n’était pas exécutée.
Apprendre sur Usermanager et ApplicationUser
Continuer avec notre tournée Program.cs
Dans le projet de serveur, la ligne suivante que vous pouvez voir est l’enregistrement dans le DI du IdentityUserAccessor
classe:
builder.Services.AddScoped<IdentityUserAccessor>();
En examinant cette classe qui fait partie de notre projet, vous pouvez voir qu’elle a la structure suivante:
internal sealed class IdentityUserAccessor(UserManager<ApplicationUser> userManager, IdentityRedirectManager redirectManager)
{
public async Task<ApplicationUser> GetRequiredUserAsync(HttpContext context)
{
var user = await userManager.GetUserAsync(context.User);
if (user is null)
{
redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context);
}
return user;
}
}
À ce stade, le code peut commencer à sembler un peu déroutant en raison de l’utilisation des classes. Pour éliminer les doutes, je vais expliquer certains concepts de base.
Tout d’abord, vous devez savoir que l’architecture d’identité de base ASP.NET est composée de classes et de magasins Manager. Le documentation officielle Indique que les gestionnaires sont des classes de haut niveau qu’un développeur peut utiliser pour effectuer des opérations, comme la création d’un utilisateur d’identité.
Pour en revenir au code source, vous pouvez voir que comme le premier paramètre, il reçoit un paramètre de type UserManager
qui est une classe de gestionnaire que nous avons décrite plus tôt. Si nous examinons cette classe de près, nous pouvons trouver des méthodes pour créer des utilisateurs, les mettre à jour, les supprimer, la gestion des confirmations de courriels et une énorme liste de méthodes. Voici une partie des méthodes disponibles dans le cadre de cette classe:
Un autre point important est que la définition du UserManager
La classe reçoit un générique de type TUser
:
public class UserManager<TUser> : IDisposable where TUser : class
Dans le modèle, vous pouvez voir que le générique utilisé est le ApplicationUser
classe. Pour mieux comprendre ce qu’un ApplicationUser
fait référence à, il est temps de parler des magasins. Encore une fois, selon la documentation officielle, les magasins sont des classes de bas niveau qui spécifient comment les utilisateurs et les rôles sont persistés. Ces magasins suivent le modèle de référentiel, sont fortement liés aux mécanismes de persistance et utilisent un IdentityUser
pour travailler avec le modèle défini.
Une information très importante qui vous aidera si vous souhaitez remplacer le mécanisme de persistance est que les gestionnaires sont découplés des magasins, ce qui signifie que chaque fois que vous le souhaitez, vous pouvez remplacer le mécanisme de persistance sans affecter votre code d’application.
Une fois que nous le savons, si nous examinons le ApplicationUser
classe, nous voyons que c’est une classe vide qui hérite de IdentityUser
:
public class ApplicationUser : IdentityUser
{
}
IdentityUser
est une classe de base qui représente un utilisateur dans ASP.NET Core Identity et fournit des propriétés de base nécessaires à la gestion des utilisateurs telles que UserName
, PasswordHash
etc. Vous vous demandez peut-être à ce stade pourquoi le ApplicationUser
La classe est vide; La réponse est pour que vous n’êtes pas lié à la classe de base et que vous puissiez s’étendre ApplicationUser
en ajoutant vos propres propriétés.
Une fois que nous comprenons les concepts ci-dessus, nous pouvons conclure que le but de l’enregistrement d’identité accessoire est d’obtenir l’utilisateur authentifié actuel via le GetRequiredUserAsync
méthode.
Comprendre la classe IdentityRedIrectManager
Dans IdentityUserAccessor
classe que nous avons vue dans la section précédente, vous avez peut-être remarqué qu’un IdentityRedirectManager
Le type est utilisé comme paramètre. De même, dans Program.cs
Cette même classe est enregistrée comme suit:
builder.Services.AddScoped<IdentityRedirectManager>();
Il s’agit d’une classe très utile qui permet d’effectuer des redirectes dans l’application de manière simple, comme dans l’exemple suivant:
RedirectManager.RedirectTo("Account/ForgotPasswordConfirmation");
Comprendre l’authentificationStateProvider, plus persistingServerAuthenticationStateProvider et PersistantAuthenticationStateProvider Classes
Poursuivre l’analyse de Program.cs
la ligne suivante que nous analyserons est l’enregistrement de PersistingServerAuthenticationStateProvider
. À ce stade, il est fondamental de comprendre un autre concept d’identité de base ASP.NET: quel AuthenticationStateProvider
est. Fondamentalement, ce sont des services fondamentaux qui fournissent des informations sur l’état d’authentification de l’utilisateur. Composants comme AuthorizeView
s’appuyer sur les informations de ce service pour décider de montrer ou non des informations à un utilisateur, car un AuthenticationStateProvider
Fournit des informations sur un utilisateur ClaimsPrincipal
.
Examinant le PersistingServerAuthenticationStateProvider
Classe plus en détail, nous pouvons remarquer un champ interne appelé état de type PersistentComponentState
ce qui nous aidera à transmettre des données d’authentification entre le serveur et le client:
private readonly PersistentComponentState state;
Nous pouvons mieux voir cela si nous examinons le OnPersistingAsync
méthode:
private async Task OnPersistingAsync()
{
if (authenticationStateTask is null)
{
throw new UnreachableException($"Authentication state not set in {nameof(OnPersistingAsync)}().");
}
var authenticationState = await authenticationStateTask;
var principal = authenticationState.User;
if (principal.Identity?.IsAuthenticated == true)
{
var userId = principal.FindFirst(options.ClaimsIdentity.UserIdClaimType)?.Value;
var email = principal.FindFirst(options.ClaimsIdentity.EmailClaimType)?.Value;
if (userId != null && email != null)
{
state.PersistAsJson(nameof(UserInfo), new UserInfo
{
UserId = userId,
Email = email,
});
}
}
}
Dans la méthode ci-dessus, en résumé, si un utilisateur s’authentifie correctement du côté du serveur, les informations sont persistées comme JSON dans le PersistentComponentState
. Il convient de noter que tout ce code, faisant partie de votre projet, peut être modifié au besoin, persistant d’autres types d’informations si nécessaire.
Maintenant, dans le projet côté client, vous pouvez voir que nous avons également un Program.cs
déposer. Dans ce fichier qui contient moins de lignes que le projet serveur, il y a une ligne où le PersistentAuthenticationStateProvider
le type est enregistré:
builder.Services.AddSingleton<AuthenticationStateProvider, PersistentAuthenticationStateProvider>();
Regarder de près PersistentAuthenticationStateProvider
nous pouvons voir que du côté client, il essaie d’obtenir les données de la PersistentComponentState
Cela avait déjà persisté du côté serveur:
public PersistentAuthenticationStateProvider(PersistentComponentState state)
{
if (!state.TryTakeFromJson<UserInfo>(nameof(UserInfo), out var userInfo) || userInfo is null)
{
return;
}
Claim[] claims = [
new Claim(ClaimTypes.NameIdentifier, userInfo.UserId),
new Claim(ClaimTypes.Name, userInfo.Email),
new Claim(ClaimTypes.Email, userInfo.Email) ];
authenticationStateTask = Task.FromResult(
new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims,
authenticationType: nameof(PersistentAuthenticationStateProvider)))));
}
C’est ainsi que le projet client parvient à obtenir des informations sur l’authentification qui a été exécutée du côté serveur, ce qui nous permet de personnaliser ces classes autant que nous en avons besoin.
Sur les méthodes d’adaptorisation et d’adautshentification
Poursuivre notre voyage à travers Program.cs
Côté serveur, nous pouvons trouver l’exécution du AddAuthorization
et AddAuthentication
Méthodes. La première méthode permet de définir des politiques d’autorisation pour les composants:
builder.Services.AddAuthorization();
D’autre part, AddAuthentication
est responsable de la configuration des services d’authentification, de la définition des schémas d’authentification, et il est possible de le configurer avec différents fournisseurs comme les cookies et les jetons.
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies();
Dans l’exemple de modèle, un cookie d’authentification est défini avec des schémas définis dans le ApplicationScheme
et ExternalScheme
Les constantes, ce qui signifie que ces schémas ou noms apparaîtront pour les cookies qui seront enregistrés dans le navigateur une fois que le client sera authentifié correctement.
Spécification du fournisseur de données pour la persistance des données
Après avoir exécuté les méthodes d’authentification et d’autorisation, il est temps de définir la base de données pour persister les informations. Bien qu’une base de données SQL Server ait été créée, il est possible de modifier ce fournisseur pour un autre. Par exemple, si ce même modèle est créé à partir du code Visual Studio, le fournisseur par défaut sera SQLite.
Examinons un peu le code du modèle:
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
Le code ci-dessus sera familier si vous avez déjà travaillé avec Entity Framework. Nous pouvons naviguer vers le contexte appelé ApplicationDbContext
où vous pouvez voir sa définition suivante:
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : IdentityDbContext<ApplicationUser>(options)
{
}
Vous pouvez voir que le contexte de l’application hérite de IdentityDbContext
qui est une classe spécialisée qui comprend tout ce qui est nécessaire pour gérer les identités, les rôles et les réclamations des utilisateurs. C’est ce qui provoque des tables comme AspNetUsers
, AspNetRoles
etc. Pour être disponible lorsque vous exécutez l’application. De même, vous pouvez voir que comme un générique, un ApplicationUser
est utilisé qui représente un utilisateur et que nous avons examiné précédemment. C’est ainsi que la base de données qui stocke les utilisateurs de l’application est créée.
Examiner AddIdentityCore, AddentityFrameworkStores, AddsignManager et AddDefaultTokenProviders Méthodes
Dans Program.cs
Fichier du côté serveur, vous pouvez voir que les méthodes suivantes sont exécutées:
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
La fonction des méthodes ci-dessus est la suivante:
AddIdentityCore
: Ajoute les services d’identité de base, la configurationApplicationUser
comme classe pour les utilisateurs. De plus, cela indique que les comptes doivent être confirmés pour pouvoir se connecter.AddEntityFrameworkStores
: Configure l’identité de l’entité Framework pour être le mécanisme de stockage des informations, spécifiantApplicationDbContext
comme contexte de la base de données. C’est ce qui permet au service d’identité d’interagir avec la base de données pour stocker les informations, les rôles, etc.AddSignInManager
: Ajoute leSignInManager
Service au conteneur DI, qui contient des méthodes pour vous connecter et sortir, générer des jetons, vérifier les mots de passe, etc.AddDefaultTokenProviders
: Active la génération de jetons pour effectuer des opérations telles que la réinitialisation du mot de passe, la confirmation par e-mail, l’authentification à deux facteurs, etc.
Informations supplémentaires sur l’authentification dans Blazor WebAssembly
À la fin du Program.cs
Fichier, vous pouvez remarquer que la ligne suivante est exécutée:
app.MapAdditionalIdentityEndpoints();
Si nous examinons cette classe, nous voyons qu’il contient une seule méthode qui définit les points d’extrémité requis par les composants du rasoir d’identité. N’oubliez pas que bien qu’il s’agisse d’une application Blazor WebAssembly, vous devez toujours effectuer des opérations d’authentification des utilisateurs côté serveur. Il s’agit de l’un des mécanismes qui permet d’héberger des points de terminaison d’authentification du côté serveur. Voici une partie de cette classe:
internal static class IdentityComponentsEndpointRouteBuilderExtensions
{
public static IEndpointConventionBuilder MapAdditionalIdentityEndpoints(this IEndpointRouteBuilder endpoints)
{
ArgumentNullException.ThrowIfNull(endpoints);
var accountGroup = endpoints.MapGroup("/Account");
accountGroup.MapPost("/PerformExternalLogin", (
HttpContext context,
[FromServices] SignInManager<ApplicationUser> signInManager,
[FromForm] string provider,
[FromForm] string returnUrl) =>
{
IEnumerable<KeyValuePair<string, StringValues>> query = [
new("ReturnUrl", returnUrl),
new("Action", ExternalLogin.LoginCallbackAction)];
var redirectUrl = UriHelper.BuildRelative(
context.Request.PathBase,
"/Account/ExternalLogin",
QueryString.Create(query));
var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return TypedResults.Challenge(properties, [provider]);
});
...
}
}
Peut-être qu’une autre question que vous pourriez avoir à ce stade est que si l’application a été créée en spécifiant un mode de rendu Global WebAssembly, tous les composants, y compris ceux d’authentification, ne devraient-ils pas être rendus de cette façon? Pour répondre à cette question, nous devons aller au App.razor
fichier dans le projet serveur. Ici, vous pouvez voir la ligne suivante:
private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
? null
: InteractiveWebAssembly;
Dans le code ci-dessus, ce qui se fait /Account
L’itinéraire ne doit pas se comporter comme s’il s’agissait de composants WebAssembly, qui sert à protéger les composants d’authentification contre les attaques côté client.
En utilisant des composants d’authentification
Une fois que nous connaissons les principes fondamentaux de l’authentification, vous devez savoir que grâce à tout ce qui précède, il est possible d’afficher ou de masquer des informations aux utilisateurs en fonction de leur état d’authentification. Vous pouvez clairement voir cela si vous accédez au projet client et ouvrez le Auth.razor
page. Ce fichier ressemble à ceci:
@page "/auth"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
<PageTitle>Auth</PageTitle>
<h1>You are authenticated</h1>
<AuthorizeView>
Hello @context.User.Identity?.Name!
</AuthorizeView>
Dans le code ci-dessus, le premier attribut [Authorize]
sert à bloquer le rendu de la page entière à tout utilisateur non authentifié. D’un autre côté, le AuthorizeView
le composant permet de nicher le Authorized
et NotAuthorized
Composants pour afficher différents blocs de contenu en fonction de l’état d’authentification. Enfin, AuthorizeView
Permet d’extraire des informations sur les utilisateurs authentifiés, comme dans l’exemple suivant:
@page "/auth"
@using Microsoft.AspNetCore.Authorization
<PageTitle>Auth</PageTitle>
<h1>You are authenticated</h1>
<AuthorizeView>
<Authorized>
Hello @context.User.Identity?.Name!
</Authorized>
<NotAuthorized>
You are not authenticated!
</NotAuthorized>
</AuthorizeView>
Le résultat de la page ci-dessus est la suivante:
Conclusion
Tout au long de cet article, vous avez appris les bases de l’authentification Blazor WebAssembly, y compris des concepts importants concernant le système d’authentification de l’identité. C’est certainement le bon moment pour explorer l’exemple du projet en mettant les connaissances acquises en pratique et en apprenant beaucoup plus en cours de route.
Source link