Sécuriser ASP.NET Core Blazor WebAssembly avec ASP.NET Core Identity

Les applications autonomes Blazor WebAssembly peuvent être sécurisées avec ASP.NET Core Identity en suivant les instructions de cet article.

Points de terminaison pour l’inscription, la connexion et la déconnexion

Au lieu d’utiliser l’interface utilisateur par défaut fournie par ASP.NET Core Identity pour les applications SPA et Blazor, qui est basée sur Razor Pages, appelez MapIdentityApi une API back-end pour ajouter des JSpoints de terminaison d’API ON pour l’inscription et la journalisation des utilisateurs avec ASP.NET Core Identity. Les points de terminaison d’API Identity prennent également en charge les fonctionnalités avancées, telles que l’authentification à deux facteurs et la vérification par e-mail.

Sur le client, appelez le point de terminaison /register pour inscrire un utilisateur avec son adresse e-mail et son mot de passe :

var result = await _httpClient.PostAsJsonAsync(
    "register", new
    {
        email,
        password
    });

Sur le client, connectez-vous à un utilisateur avec l’authentification cookie à l’aide du point de terminaison /login avec la chaîne de requête useCookies définie sur true :

var result = await _httpClient.PostAsJsonAsync(
    "login?useCookies=true", new
    {
        email,
        password
    });

L’API du serveur principal établit l’authentification cookie avec un appel à AddIdentityCookies sur le générateur d’authentification :

builder.Services
    .AddAuthentication(IdentityConstants.ApplicationScheme)
    .AddIdentityCookies();

Jeton d’authentification

Pour les scénarios natifs et mobiles dans lesquels certains clients ne prennent pas en charge les cookies, l’API de connexion fournit un paramètre pour demander des jetons. Un jeton personnalisé (privé pour la plateforme ASP.NET Core Identity) est émis et peut être utilisé pour authentifier les requêtes suivantes. Vous devez passer le jeton dans l’en-tête Authorization en tant que jeton du porteur. Un jeton d’actualisation est également fourni. Ce jeton permet à l’application de demander un nouveau jeton lorsque l’ancien expire sans forcer l’utilisateur à se reconnecter.

Les jetons ne sont pas des jetons JSON Web Tokens (JWT) standard. L’utilisation de jetons personnalisés est intentionnelle, car l’API intégrée Identity est destinée principalement aux scénarios simples. L’option de jeton n’est pas destinée à être un fournisseur de services d’identité ou un serveur de jetons complet, mais plutôt une alternative à l’option cookie pour les clients qui ne peuvent pas utiliser les cookie.

L’aide suivante vous permet de démarrer le processus d’implémentation de l’authentification par jeton avec l’API de connexion. L’ajout de code personnalisé est nécessaire pour terminer l’implémentation. Pour plus d’informations, consultez Utiliser Identity pour sécuriser un back-end d’API web pour des applications monopage.

L’API du serveur principal n’établit pas l’authentification par cookie avec un appel à AddIdentityCookies sur le générateur d’authentification. Au lieu de cela, l’API du serveur configure l’authentification par jeton du porteur avec la méthode d’extension AddBearerToken. Spécifiez le schéma pour les jetons d’authentification du porteur avec IdentityConstants.BearerScheme.

Dans Backend/Program.cs, remplacez les services d’authentification et la configuration par ce qui suit :

builder.Services
    .AddAuthentication()
    .AddBearerToken(IdentityConstants.BearerScheme);

Dans BlazorWasmAuth/Identity/CookieAuthenticationStateProvider.cs, supprimez le paramètre de chaîne de requête useCookies dans la méthode LoginAsync de CookieAuthenticationStateProvider :

- login?useCookies=true
+ login

À ce stade, vous devez fournir du code personnalisé pour analyser AccessTokenResponse sur le client et gérer les jetons d’accès et d’actualisation. Pour plus d’informations, consultez Utiliser Identity pour sécuriser un back-end d’API web pour des applications monopage.

Autres scénarios liés à Identity

Pour obtenir d’autres scénarios liés à Identity fournis par l’API, consultez Utiliser Identity pour sécuriser un back-end d’API web pour les applications monopages :

  • Sécuriser les points de terminaison sélectionnés
  • Jeton d’authentification
  • Authentification à deux facteurs (2FA)
  • des codes de récupération
  • Gestion des informations utilisateur

Exemples d’applications

Dans cet article, les exemples d’applications servent de référence pour les applications autonomes Blazor WebAssembly qui accèdent à ASP.NET Core Identity par le biais d’une API web principale. La démonstration comprend deux applications :

  • Backend : application API web principale qui gère un magasin d’identités utilisateur pour ASP.NET Core Identity.
  • BlazorWasmAuth : application frontale autonome Blazor WebAssembly avec authentification utilisateur.

Accédez aux exemples d’applications par le biais du dossier de version le plus récent à partir de la racine du référentiel avec le lien suivant. Les exemples sont fournis pour .NET 8 ou version ultérieure. Reportez-vous au README fichier dans le BlazorWebAssemblyStandaloneWithIdentity dossier pour connaître les étapes d’exécution des exemples d’applications.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Packages et code d’application API web back-end

L’application API web principale gère un magasin d’identités utilisateur pour ASP.NET Core Identity.

Packages

L’application utilise les packages NuGet suivants :

Si votre application doit utiliser un autre EF Core fournisseur de base de données que le fournisseur en mémoire, ne créez pas de référence de package dans votre application pour Microsoft.EntityFrameworkCore.InMemory.

Dans le fichier projet de l’application (.csproj), la globalisation invariante est configurée.

Exemple de code d’application

Les paramètres d’application configurent les URL dorsale et frontale :

  • Backend application (BackendUrl) : https://localhost:7211
  • BlazorWasmAuth application (FrontendUrl) : https://localhost:7171

Le Backend.http fichier peut être utilisé pour tester la requête de données météorologiques. Notez que l’application BlazorWasmAuth doit s’exécuter pour tester le point de terminaison et que le point de terminaison est codé en dur dans le fichier. Pour en savoir plus, reportez-vous à Utiliser des fichiers .http dans Visual Studio 2022.

L’installation et la configuration suivantes se trouvent dans le fichier de Programl’application.

L’identité de l’utilisateur avec cookie authentification est ajoutée en appelant AddAuthentication et AddIdentityCookies. Les services pour les vérifications d’autorisation sont ajoutés par un appel à AddAuthorizationBuilder.

Uniquement recommandé pour les démonstrations, l’application utilise le EF Corefournisseur de base de données en mémoire pour l’inscription du contexte de base de données (AddDbContext). Le fournisseur de base de données en mémoire facilite le redémarrage de l’application et teste les flux utilisateur d’inscription et de connexion. Chaque exécution démarre avec une nouvelle base de données, mais l’application comprend un code de démonstration de l’ensemencement par l’utilisateur(-trice), qui est décrit plus loin dans cet article. Si la base de données est remplacée par SQLite, les utilisateurs sont enregistrés entre les sessions, mais la base de données doit être créée par le biais des migrations, comme indiqué dans le EF Core didacticiel de démarrage. Vous pouvez utiliser d’autres fournisseurs relationnels tels que SQL Server pour votre code de production.

Configurez Identity pour utiliser la base de données EF Core et exposer les points de terminaison Identity via les appels à AddIdentityCore, AddEntityFrameworkStores et AddApiEndpoints.

Une politique de partage des ressources entre origines multiples (CORS) est définie pour autoriser les demandes provenant des applications dorsale et frontale. Les URL de secours sont configurées pour la stratégie CORS si les paramètres de l’application ne les fournissent pas :

  • Backend application (BackendUrl) : https://localhost:5001
  • BlazorWasmAuth application (FrontendUrl) : https://localhost:5002

Les services et les points de terminaison pour Swagger/OpenAPI sont inclus pour la documentation de l’API web et les tests de développement. Pour plus d’informations sur NSwag, consultez Bien démarrer avec NSwag et ASP.NET Core.

Les revendications de rôle d’utilisateur sont envoyées à partir d’une API minimale au niveau du /roles point de terminaison.

Les itinéraires sont mappés pour Identity les points de terminaison en appelant MapIdentityApi<AppUser>().

Un point de terminaison de déconnexion (/Logout) est configuré dans le pipeline de l’intergiciel pour déconnecter les utilisateurs.

Pour sécuriser un point de terminaison, ajoutez la RequireAuthorization méthode d’extension à la définition de routage. Pour un contrôleur, ajoutez l’attribut [Authorize] au contrôleur ou à l’action.

Pour en savoir plus sur les modèles de base pour l’initialisation et la configuration d’une DbContext instance, reportez-vous à DbContext Lifetime, Configuration et Initialization dans la EF Core documentation.

Packages et code d’application Blazor WebAssembly autonome frontend

Une application frontale autonome Blazor WebAssembly illustre l’authentification et l’autorisation des utilisateurs pour accéder à une page web privée.

Packages

L’application utilise les packages NuGet suivants :

Exemple de code d’application

Le Models dossier contient les modèles de l’application :

L’interface IAccountManagement (Identity/CookieHandler.cs) fournit des services de gestion de compte.

La classe CookieAuthenticationStateProvider (Identity/CookieAuthenticationStateProvider.cs) gère l’état de cookiel’authentification basée sur les comptes et fournit des implémentations de service de gestion des comptes décrites par l’interface IAccountManagement. La méthode LoginAsync active explicitement l’authentification cookie via la valeur de chaîne de requête useCookies de true. La classe gère également la création de revendications de rôle pour les utilisateurs authentifiés.

La classe CookieHandler (Identity/CookieHandler.cs) garantit que les informations d’identification cookie sont envoyées avec chaque requête à l’API web principale, qui gère Identity et gère le magasin de données Identity.

wwwroot/appsettings.file fournit des points de terminaison d’URL frontale et dorsale

Le composantApp expose l’état d’authentification en tant que paramètre en cascade. Pour plus d’informations, consultez ASP.NET Core Blazor Authentification et autorisation.

Le MainLayout composant et NavMenule composant utilisent le AuthorizeViewcomposant pour afficher de manière sélective du contenu en fonction de l’état d’authentification de l’utilisateur.

Les composants suivants gèrent les tâches courantes d’authentification des utilisateurs, utilisant des IAccountManagement services :

Le composant PrivatePage (Components/Pages/PrivatePage.razor) nécessite l’authentification et affiche les revendications de l’utilisateur.

Les services et la configuration sont fournis dans le fichier Program (Program.cs) :

  • Le gestionnaire cookie est inscrit en tant que service étendu.
  • Les services d’autorisation sont inscrits.
  • Le fournisseur d’état d’authentification personnalisé est inscrit en tant que service étendu.
  • L’interface de gestion de compte (IAccountManagement) est inscrite.
  • L’URL de l’hôte de base est configurée pour une instance de client HTTP inscrite.
  • L’URL principale de base est configurée pour une instance de client HTTP inscrite utilisée pour les interactions d’authentification avec l’API web principale. Le client HTTP utilise le cookie gestionnaire pour s’assurer que cookie les informations d’identification sont envoyées avec chaque requête.

Appelez AuthenticationStateProvider.NotifyAuthenticationStateChanged lorsque l’état d’authentification de l’utilisateur change. Pour obtenir un exemple, reportez-vous aux LoginAsync et aux LogoutAsync méthodes de la CookieAuthenticationStateProviderclasse (Identity/CookieAuthenticationStateProvider.cs ).

Avertissement

Le composant AuthorizeView affiche de manière sélective le contenu d’interface utilisateur en fonction de l’autorisation dont dispose l’utilisateur. Tout le contenu d’une Blazor WebAssembly application placé dans un AuthorizeView composant est détectable sans authentification. Le contenu sensible doit donc être obtenu à partir d’une API web basée sur un serveur principal une fois l’authentification terminée. Pour plus d’informations, consultez les ressources suivantes :

Démonstration d’amorçage d’utilisateurs de test

La SeedData classe (SeedData.cs) montre comment créer des utilisateurs de test pour le développement. L’utilisateur(-trice) de test, nommé Leela, se connecte à l’application avec l’adresse e-mail leela@contoso.com. Le mot de passe de l’utilisateur(-trice) est défini sur Passw0rd!. Leela est donné des rôles Administrator et Manager pour l’autorisation, ce qui permet à l’utilisateur(-trice) d’accéder à la page du gestionnaire, /private-manager-page mais pas à la page de l’éditeur(-trice) à l’adresse /private-editor-page.

Avertissement

N’autorisez jamais l’exécution du code utilisateur de test dans un environnement de production. SeedData.InitializeAsync est uniquement appelé dans l’environnement Development dans le Program fichier :

if (builder.Environment.IsDevelopment())
{
    await using var scope = app.Services.CreateAsyncScope();
    await SeedData.InitializeAsync(scope.ServiceProvider);
}

Rôles

En raison d’un problème de conception d’infrastructure (dotnet/aspnetcore #50037), les revendications de rôle ne sont pas renvoyées à partir du manage/info point de terminaison pour créer des revendications utilisateur pour les utilisateurs de l’application BlazorWasmAuth. Les revendications de rôle sont gérées indépendamment via une requête distincte dans la GetAuthenticationStateAsync méthode de la CookieAuthenticationStateProvider classe (Identity/CookieAuthenticationStateProvider.cs) après l’authentification de l’utilisateur dans le Backend projet.

Dans le CookieAuthenticationStateProvider, une requête de rôles est adressée au /roles point de terminaison du projet d’API Backend serveur. La réponse est lue dans une chaîne en appelant ReadAsStringAsync(). JsonSerializer.Deserialize désérialise la chaîne dans un tableau personnalisé RoleClaim. Enfin, les revendications sont ajoutées à la collection de revendications de l’utilisateur(-trice).

Dans le fichier de l’BackendAPI du serveurProgram, une API minimale gère le /roles point de terminaison. Les revendications de RoleClaimType sont sélectionnées dans un type anonyme et sérialisées pour revenir au BlazorWasmAuth projet avec TypedResults.Json.

Le point de terminaison des rôles demande une autorisation en appelant RequireAuthorization. Si vous décidez de ne pas utiliser d’API minimales en faveur des contrôleurs pour les points de terminaison d’API de serveur sécurisés, veillez à définir l’[Authorize]attribut sur les contrôleurs ou les actions.

Hébergement inter-domaines (configuration de même site)

Les exemples d’applications sont configurés pour héberger les deux applications dans le même domaine. Si vous hébergez l’application Backend dans un domaine différent de celui de l’application BlazorWasmAuth, supprimez les marques de commentaire du code qui configure le cookie (ConfigureApplicationCookie) dans le fichier de Backend l’applicationProgram. Les valeurs par défaut sont :

Remplacez les valeurs par :

- options.Cookie.SameSite = SameSiteMode.Lax;
- options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
+ options.Cookie.SameSite = SameSiteMode.None;
+ options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

Pour plus d’informations sur les paramètres cookie de même site, consultez les ressources suivantes :

Prise en charge d’Antiforgery

Seul le point de terminaison de déconnexion (/logout) dans l’application Backend nécessite une attention particulière pour atténuer la menace de falsification de requête intersites (CSRF).

Le point de terminaison de déconnexion recherche un corps vide afin d’empêcher les attaques CSRF. En exigeant un corps, la requête doit être effectuée à partir de JavaScript, qui est le seul moyen d’accéder à l’authentification cookie. Le point de terminaison de déconnexion n’est pas accessible par un POST basé sur un formulaire. Cela empêche un site malveillant de déconnecter l’utilisateur.

En outre, le point de terminaison est protégé par autorisation (RequireAuthorization) pour empêcher un accès anonyme.

L’application cliente BlazorWasmAuth est simplement nécessaire pour passer un objet vide {} dans le corps de la requête.

En dehors du point de terminaison de déconnexion, l’atténuation de la menace de falsification n’est nécessaire que lors de l’envoi de données de formulaire au serveur encodé en tant que application/x-www-form-urlencoded, multipart/form-data ou text/plain. Blazor gère l’atténuation CSRF pour les formulaires dans la plupart des cas. Pour plus d’informations, consultez Authentification et autorisation Blazor ASP.NET Core et la vue d’ensemble des formulaires BlazorASP.NET Core.

Les requêtes adressées à d’autres points de terminaison d’API serveur (API web) avec un contenu application/json encodé et CORS activé ne nécessitent pas de protection CSRF. C’est pourquoi aucune protection CSRF n’est requise pour le point de terminaison de traitement des données de l’application Backend (/data-processing). Le point de terminaison des rôles (/roles) n’a pas besoin de la protection CSRF, car il s’agit d’un point de terminaison GET qui ne modifie aucun état.

Résolution des problèmes

Journalisation

Pour activer la journalisation du débogage ou des traces dans le cadre de l’authentification Blazor WebAssembly, consultez Journalisation ASP.NET Core Blazor.

Erreurs courantes

Vérifiez la configuration de chaque projet. Confirmez que les URL sont correctes :

  • Projet Backend
    • appsettings.json
      • BackendUrl
      • FrontendUrl
    • Backend.http: Backend_HostAddress
  • Projet BlazorWasmAuth : wwwroot/appsettings.json
    • BackendUrl
    • FrontendUrl

Si la configuration semble correcte :

  • Analysez les journaux des applications.

  • Examinez le trafic réseau entre l’application BlazorWasmAuth et l’application Backend à l’aide des outils de développement du navigateur. Un message d’erreur exact ou un message indiquant la cause du problème est souvent retourné au client par l’application back-end à la suite d’une demande. Vous trouverez des conseils d’aide sur les outils de développement dans les articles suivants :

  • Google Chrome (documentation Google)

  • Microsoft Edge

  • Mozilla Firefox (documentation Mozilla)

L’équipe Documentation répond pour documenter les commentaires et les bogues des articles. Ouvrez un problème en utilisant le lien Ouvrir un problème de documentation en bas de l’article. L’équipe n’est pas en mesure de fournir un support technique. Plusieurs forums de support publics sont disponibles pour vous aider à résoudre les problèmes liés à une application. Nous recommandons ce qui suit :

Les forums précédents ne sont pas détenus ou contrôlés par Microsoft.

Pour les rapports de bogues de framework reproductibles, non liés à la sécurité, non sensibles et non confidentiels, ouvrez un problème auprès de l’unité de produit ASP.NET Core. N’ouvrez pas de problème auprès de l’unité de produit tant que vous n’avez pas investigué de manière approfondie la cause du problème, sans pouvoir le résoudre par vous-même ou avec l’aide de la communauté sur un forum de support public. L’unité de produit ne peut pas résoudre les problèmes d’applications individuelles qui sont défaillantes en raison d’une mauvaise configuration ou de cas d’usage impliquant des services tiers. Si un rapport est de nature sensible ou confidentielle, ou s’il décrit une faille de sécurité potentielle dans le produit que des attaquants peuvent exploiter, consultez Signalement des problèmes de sécurité et des bogues (dépôt GitHub dotnet/aspnetcore).

Cookies et données de site

Les Cookies et les données de site peuvent persister entre les mises à jour d’application, et interférer avec les tests et la résolution des problèmes. Effacez ce qui suit quand vous apportez des changements au code d’application, au compte d’utilisateur ou à la configuration de l’application :

  • cookies de connexion de l’utilisateur
  • cookies d’application
  • Données de site mises en cache et stockées

Il existe une approche qui permet d’empêcher les cookie et les données de site persistants d’interférer avec les tests et la résolution des problèmes. Elle consiste à :

  • Configurer un navigateur
    • Utilisez un navigateur de test que vous pouvez configurer pour supprimer tous les cookies et toutes les données de site à chaque fois qu’il se ferme.
    • Vérifiez que le navigateur est fermé manuellement ou par l’IDE chaque fois qu’un changement est apporté à la configuration de l’application, de l’utilisateur de test ou du fournisseur.
  • Utilisez une commande personnalisée pour ouvrir un navigateur en mode InPrivate ou Incognito dans Visual Studio :
    • Ouvrez la boîte de dialogue Parcourir avec à partir du bouton Exécuter de Visual Studio.
    • Cliquez sur le bouton Ajouter.
    • Indiquez le chemin de votre navigateur dans le champ Programme. Les chemins d’exécutables suivants sont des emplacements d’installation classiques de Windows 10. Si votre navigateur est installé à un autre emplacement, ou si vous n’utilisez pas Windows 10, indiquez le chemin de l’exécutable du navigateur.
      • Microsoft Edge : C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome : C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox : C:\Program Files\Mozilla Firefox\firefox.exe
    • Dans le champ Arguments, indiquez l’option de ligne de commande utilisée par le navigateur pour qu’il s’ouvre en mode InPrivate ou Incognito. Certains navigateurs nécessitent l’URL de l’application.
      • Microsoft Edge : Utilisez -inprivate.
      • Google Chrome : Utilisez --incognito --new-window {URL}, où l’espace réservé {URL} correspond à l’URL à ouvrir (par exemple https://localhost:5001).
      • Mozilla Firefox : Utilisez -private -url {URL}, où l’espace réservé {URL} correspond à l’URL à ouvrir (par exemple https://localhost:5001).
    • Indiquez un nom dans le champ Nom convivial. Par exemple, Firefox Auth Testing
    • Cliquez sur le bouton OK.
    • Pour éviter d’avoir à sélectionner le profil de navigateur pour chaque itération de test avec une application, définissez le profil en tant que profil par défaut avec le bouton Par défaut.
    • Vérifiez que le navigateur est fermé par l’IDE chaque fois qu’un changement est apporté à la configuration de l’application, de l’utilisateur de test ou du fournisseur.

Mises à niveau d’application

Une application fonctionnelle peut échouer immédiatement après la mise à niveau du kit SDK .NET Core sur l’ordinateur de développement ou la modification des versions de package au sein de l’application. Dans certains cas, les packages incohérents peuvent bloquer une application quand vous effectuez des mises à niveau majeures. Vous pouvez résoudre la plupart de ces problèmes en suivant les instructions suivantes :

  1. Effacez les caches de package NuGet du système local en exécutant dotnet nuget locals all --clear à partir d’un interpréteur de commandes.
  2. Supprimez les dossiers bin et obj du projet.
  3. Restaurez et regénérez le projet.
  4. Supprimez tous les fichiers du dossier de déploiement sur le serveur avant de redéployer l’application.

Remarque

L’utilisation de versions de package incompatibles avec le framework cible de l’application n’est pas prise en charge. Pour plus d’informations sur un package, utilisez la Galerie NuGet ou l’Explorateur de packages FuGet.

Inspecter les revendications des utilisateurs

Pour résoudre les problèmes liés aux revendications des utilisateurs, le composant UserClaims suivant peut être utilisé directement dans les applications ou servir de base à une personnalisation.

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@attribute [Authorize]

<PageTitle>User Claims</PageTitle>

<h1>User Claims</h1>

**Name**: @AuthenticatedUser?.Identity?.Name

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())
{
    <p class="claim">@(claim.Type): @claim.Value</p>
}

@code {
    [CascadingParameter]
    private Task<AuthenticationState>? AuthenticationState { get; set; }

    public ClaimsPrincipal? AuthenticatedUser { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (AuthenticationState is not null)
        {
            var state = await AuthenticationState;
            AuthenticatedUser = state.User;
        }
    }
}

Ressources supplémentaires