Sécuriser une application ASP.NET Core Blazor WebAssembly hébergée avec Identity Server

Cet article explique comment créer une solution Blazor WebAssembly hébergée qui utilise Duende Identity Server pour authentifier les utilisateurs et les appels d’API.

Important

Duende Software peut vous demander de payer des frais de licence pour une utilisation en production de Duende Identity Server. Pour plus d’informations, consultez Migrer de ASP.NET Core 5.0 vers 6.0.

Remarque

Pour configurer une application Blazor WebAssembly autonome ou hébergée pour utiliser une instance de serveur Identity externe existante, suivez les instructions de Sécuriser une application autonome ASP.NET Core Blazor WebAssembly avec la bibliothèque d’authentification.

Pour obtenir une couverture supplémentaire du scénario de sécurité, après avoir lu cet article, consultez ASP.NET Core Blazor WebAssembly scénarios de sécurité supplémentaires.

Procédure pas à pas

Les sous-sections de la procédure pas à pas expliquent comment :

  • Créer l’application Blazor
  • Exécuter l’application

Créer une application Blazor

Pour créer un projet Blazor WebAssembly avec un mécanisme d’authentification :

  1. Créer un nouveau projet.

  2. Choisissez le modèle d’application Blazor WebAssembly. Sélectionnez Suivant.

  3. Fournissez un nom de projet sans utiliser de tirets. Vérifiez que l’emplacement est correct. Cliquez sur Suivant.

    Évitez d’utiliser des tirets (-) dans le nom du projet qui interrompt la formation de l’identificateur d’application OIDC. La logique dans le modèle de projet Blazor WebAssembly utilise le nom du projet pour un identificateur d’application OIDC dans la configuration de la solution, et les tirets ne sont pas autorisés dans un identificateur d’application OIDC. La casse Pascal (BlazorSample) ou les traits de soulignement (Blazor_Sample) sont des alternatives acceptables.

  4. Dans la boîte de dialogue Informations supplémentaires, sélectionnez comptes individuels comme type d’authentification pour stocker des utilisateurs dans l’application à l’aide du système Identity de ASP.NET Core.

  5. Cochez la case ASP.NET Core Hébergé.

  6. Sélectionnez le bouton Créer pour créer l’application.

Exécuter l’application

Exécutez l’application à partir du projet Server. Quand vous utilisez Visual Studio, vous pouvez :

  • Sélectionnez la flèche déroulante vers le bas en regard du bouton Exécuter. Ouvrez Configurer des projets de démarrage dans la liste déroulante. Sélectionnez l’option Projet de démarrage unique. Confirmez ou remplacez le projet de démarrage par le projet Server.

  • Vérifiez que le projet Server est mis en surbrillance dans l’Explorateur de solutions avant de démarrer l’application avec l’une des approches suivantes :

    • Sélectionnez le bouton Run.
    • Utilisez Déboguer>Démarrer le débogage dans le menu.
    • Appuyez sur F5.
  • Dans un interpréteur de commandes, accédez au dossier de projet Server de la solution. Exécutez la commande dotnet run.

Parties de la solution

Cette section décrit les parties d’une solution générée à partir du modèle de projet Blazor WebAssembly ainsi que la façon dont les projets Client et Server de la solution sont configurés à titre de référence. Il n’existe aucun conseil d’aide spécifique à suivre dans cette section pour une application de base, si vous avez créé l’application en suivant les conseils d’aide de la section Procédure pas à pas. Les conseils d’aide de cette section sont utiles pour mettre à jour une application afin d’authentifier et d’autoriser les utilisateurs. Toutefois, une autre approche pour mettre à jour une application consiste à créer une application à partir des instructions de la section Procédure pas à pas et à déplacer les composants, les classes et les ressources de l’application vers la nouvelle application.

Services d’application Server

Cette section concerne l’application Server de la solution.

Les services suivants sont inscrits.

  • Dans le fichier Program :

    • Entity Framework Core et ASP.NET Core Identity :

      builder.Services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite( ... ));
      builder.Services.AddDatabaseDeveloperPageExceptionFilter();
      
      builder.Services.AddDefaultIdentity<ApplicationUser>(options => 
              options.SignIn.RequireConfirmedAccount = true)
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • Serveur Identity avec une méthode d’assistance supplémentaire AddApiAuthorization qui configure les conventions de ASP.NET Core par défaut sur le serveur Identity :

      builder.Services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Authentification avec une méthode d’assistance supplémentaire AddIdentityServerJwt qui configure l’application pour valider les jetons JWT produits par le serveur Identity :

      builder.Services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • Dans Startup.ConfigureServices de Startup.cs :

    • Entity Framework Core et ASP.NET Core Identity :

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(
              Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>(options => 
              options.SignIn.RequireConfirmedAccount = true)
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • Serveur Identity avec une méthode d’assistance supplémentaire AddApiAuthorization qui configure les conventions de ASP.NET Core par défaut sur le serveur Identity :

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Authentification avec une méthode d’assistance supplémentaire AddIdentityServerJwt qui configure l’application pour valider les jetons JWT produits par le serveur Identity :

      services.AddAuthentication()
          .AddIdentityServerJwt();
      

Remarque

Quand un seul schéma d’authentification est inscrit, il est automatiquement utilisé en tant que schéma par défaut de l’application. Il n’est pas nécessaire d’indiquer le schéma pour AddAuthentication ou via AuthenticationOptions. Pour plus d’informations, consultez Vue d’ensemble de l’authentification ASP.NET Core ainsi que l’Annonce ASP.NET Core (aspnet/Announcements #490).

  • Dans le fichier Program :
  • Dans Startup.Configure de Startup.cs :
  • L’intergiciel serveur Identity expose les points de terminaison OpenID Connect (OIDC) :

    app.UseIdentityServer();
    
  • L’intergiciel d’authentification est chargé de valider les informations d’identification de la demande et de définir l’utilisateur sur le contexte de la demande :

    app.UseAuthentication();
    
  • L’intergiciel d’autorisation active les fonctionnalités d’autorisation :

    app.UseAuthorization();
    

Autorisation de l’API

Cette section concerne l’application Server de la solution.

La méthode d’assistance AddApiAuthorization configure le serveur Identity pour les scénarios ASP.NET Core. Le serveur Identity est une infrastructure puissante et extensible pour gérer les problèmes de sécurité des applications. Le serveur Identity expose une complexité inutile pour les scénarios les plus courants. Par conséquent, un ensemble de conventions et d’options de configuration est fourni que nous considérons comme un bon point de départ. Une fois que vos besoins d’authentification changent, la puissance totale du serveur Identity est disponible pour personnaliser l’authentification en fonction des exigences d’une application.

Ajouter un gestionnaire d’authentification pour une API qui coexiste avec le serveur Identity

Cette section concerne l’application Server de la solution.

La méthode d’assistance AddIdentityServerJwt configure un schéma de stratégie pour l’application en tant que gestionnaire d’authentification par défaut. La stratégie est configurée pour permettre à Identity de gérer toutes les requêtes routées vers n’importe quel sous-chemin dans l’espace URL Identity sous /Identity. JwtBearerHandler gère toutes les autres requêtes. En outre, cette méthode :

  • Inscrit une ressource d’API auprès de serveur Identity avec une étendue par défaut de {PROJECT NAME}API, où l’espace réservé {PROJECT NAME} est le nom du projet lors de la création de l’application.
  • Configure l’intergiciel du jeton du porteur JWT pour valider les jetons émis par le serveur Identity pour l’application.

Contrôleur de prévision météorologique

Cette section concerne l’application Server de la solution.

Dans le WeatherForecastController (Controllers/WeatherForecastController.cs), l’attribut [Authorize] est appliqué à la classe. L’attribut indique que l’utilisateur doit être autorisé en fonction de la stratégie par défaut pour accéder à la ressource. La stratégie d’autorisation par défaut est configurée pour utiliser le schéma d’authentification par défaut, qui est configuré par AddIdentityServerJwt. La méthode d’assistance configure JwtBearerHandler en tant que gestionnaire par défaut pour les demandes adressées à l’application.

Contexte de base de données d’application

Cette section concerne l’application Server de la solution.

Dans (ApplicationDbContextData/ApplicationDbContext.cs), DbContext s’étend ApiAuthorizationDbContext<TUser> pour inclure le schéma du serveur Identity. ApiAuthorizationDbContext<TUser> est dérivé de IdentityDbContext.

Pour obtenir un contrôle total du schéma de base de données, héritez de l’une des classes IdentityDbContext disponibles et configurez le contexte pour inclure le schéma Identity en appelant builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) dans la méthode OnModelCreating.

Contrôleur de configuration OIDC

Cette section concerne l’application Server de la solution.

Dans le OidcConfigurationController (Controllers/OidcConfigurationController.cs), le point de terminaison client est approvisionné pour traiter les paramètres OIDC.

Paramètres d’application

Cette section concerne l’application Server de la solution.

Dans le fichier de paramètres de l’application (appsettings.json) à la racine du projet, la section IdentityServer décrit la liste des clients configurés. Dans l’exemple suivant, il existe un seul client. Le nom du client correspond au nom d’assembly de l’application Client et est mappé par convention au paramètre ClientId OAuth. Le profil indique le type d’application en cours de configuration. Le profil est utilisé en interne pour générer des conventions qui simplifient le processus de configuration du serveur.

"IdentityServer": {
  "Clients": {
    "{ASSEMBLY NAME}": {
      "Profile": "IdentityServerSPA"
    }
  }
}

L’espace réservé {ASSEMBLY NAME} est le nom de l’assembly de l’application Client (par exemple, BlazorSample.Client).

Package d’authentification

Cette section concerne l’application Client de la solution.

Lorsqu’une application est créée pour utiliser des comptes d’utilisateur individuels (Individual), l’application reçoit automatiquement une référence de package pour le package Microsoft.AspNetCore.Components.WebAssembly.Authentication. Le package fournit un ensemble de primitives qui aident l’application à authentifier les utilisateurs et à obtenir des jetons pour appeler des API protégées.

Si vous ajoutez l’authentification à une application, ajoutez manuellement le package Microsoft.AspNetCore.Components.WebAssembly.Authentication à l’application.

Remarque

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez les articles figurant sous Installer et gérer des packages dans Flux de travail de la consommation des packages (documentation NuGet). Vérifiez les versions du package sur NuGet.org.

Configuration HttpClient

Cette section concerne l’application Client de la solution.

Dans le fichier Program, un nom HttpClient est configuré pour fournir des instances HttpClient qui incluent des jetons d'accès lors des requêtes à l'API du serveur. Par défaut, lors de la création de la solution, le HttpClient nommé est {PROJECT NAME}.ServerAPI, où l’espace réservé {PROJECT NAME} est le nom du projet.

builder.Services.AddHttpClient("{PROJECT NAME}.ServerAPI", 
        client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("{PROJECT NAME}.ServerAPI"));

L’espace réservé {PROJECT NAME} est le nom du projet lors de la création de la solution. Par exemple, la fourniture d’un nom de projet de BlazorSample produit un HttpClient nommé de BlazorSample.ServerAPI.

Remarque

Si vous configurez une application Blazor WebAssembly pour utiliser une instance serveur Identity existante qui ne fait pas partie d’une solution Blazor hébergée, remplacez l’inscription d’adresse de base HttpClient de IWebAssemblyHostEnvironment.BaseAddress (builder.HostEnvironment.BaseAddress) par l’URL du point de terminaison d’autorisation d’API de l’application serveur.

Prise en charge des autorisations d’API

Cette section concerne l’application Client de la solution.

La prise en charge de l’authentification des utilisateurs est connectée au conteneur de service par la méthode d’extension fournie dans le package Microsoft.AspNetCore.Components.WebAssembly.Authentication. Cette méthode configure les services requis par l’application pour interagir avec le système d’autorisation existant.

builder.Services.AddApiAuthorization();

Par défaut, la configuration de l’application est chargée par convention à partir de _configuration/{client-id}. Par convention, l’ID client est défini sur le nom de l’assembly de l’application. Cette URL peut être modifiée pour pointer vers un point de terminaison distinct en appelant la surcharge avec des options.

Fichier Imports

Cette section concerne l’application Client de la solution.

L’espace de noms Microsoft.AspNetCore.Components.Authorization est disponible dans l’application via le fichier _Imports.razor :

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Page Index

Cette section concerne l’application Client de la solution.

La page d’index (wwwroot/index.html) comprend un script qui définit AuthenticationService en JavaScript. AuthenticationService gère les détails de bas niveau du protocole OIDC. L’application appelle de manière interne les méthodes définies dans le script pour effectuer les opérations d’authentification.

<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>

App (composant)

Cette section concerne l’application Client de la solution.

Le composant App (App.razor) est similaire au composant App présent dans les applications Blazor Server :

  • Le composant CascadingAuthenticationState gère l’exposition de AuthenticationState au reste de l’application.
  • Le composant AuthorizeRouteView vérifie que l’utilisateur actuel est autorisé à accéder à une page donnée, ou affiche le composant RedirectToLogin.
  • Le composant RedirectToLogin gère la redirection des utilisateurs non autorisés vers la page de connexion.

En raison des changements apportés au framework dans les différentes versions d’ASP.NET Core, la syntaxe Razor du composant App (App.razor) n’est pas affichée dans cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez l’une des approches suivantes :

  • Créez une application provisionnée pour l’authentification à partir du modèle de projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core que vous prévoyez d’utiliser. Inspectez le composant App (App.razor) dans l’application générée.

  • Inspectez le composant App (App.razor) dans source de référence. Sélectionnez la version dans le sélecteur de branche, puis recherchez le composant dans le dossier ProjectTemplates du référentiel, car il a été déplacé au fil des années.

    Remarque

    Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).

RedirectToLogin (composant)

Cette section concerne l’application Client de la solution.

Le composant RedirectToLogin (RedirectToLogin.razor) :

  • Gère la redirection des utilisateurs non autorisés vers la page de connexion.
  • L’URL actuelle à laquelle l’utilisateur tente d’accéder est conservée pour permettre à l’utilisateur de retourner à cette page en cas d’authentification réussie avec :

Inspectez le composant RedirectToLogin dans source de référence. L’emplacement du composant ayant changé au fil du temps, servez-vous des outils de recherche GitHub pour le retrouver.

Remarque

Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).

LoginDisplay (composant)

Cette section concerne l’application Client de la solution.

Le composant LoginDisplay (LoginDisplay.razor) est affiché dans le composant MainLayout (MainLayout.razor) et gère les comportements suivants :

  • Pour les utilisateurs authentifiés :
    • Affiche le nom d’utilisateur actuel.
    • Propose un lien vers la page de profil utilisateur dans ASP.NET Core Identity.
    • Propose un bouton pour se déconnecter de l’application.
  • Pour les utilisateurs anonymes :
    • Permet de s’inscrire.
    • Permet de se connecter.

En raison des changements apportés au framework dans les différentes versions d’ASP.NET Core, la syntaxe Razor du composant LoginDisplay n’est pas affichée dans cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez l’une des approches suivantes :

  • Créez une application provisionnée pour l’authentification à partir du modèle de projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core que vous prévoyez d’utiliser. Inspectez le composant LoginDisplay dans l’application générée.

  • Inspectez le composant LoginDisplay dans la source de référence. L’emplacement du composant ayant changé au fil du temps, servez-vous des outils de recherche GitHub pour le retrouver. Le contenu mis en modèle pour Hosted égal à true est utilisé.

    Remarque

    Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Authentication (composant)

Cette section concerne l’application Client de la solution.

La page produite par le composant Authentication (Pages/Authentication.razor) définit les routes nécessaires à la gestion des différentes phases d’authentification.

Le composant RemoteAuthenticatorView :

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="Action" />

@code {
    [Parameter]
    public string? Action { get; set; }
}

Remarque

Les types références null (NRT, nullable reference types) et l’analyse statique de l’état nul du compilateur .NET sont pris en charge dans ASP.NET Core dans .NET 6 ou une version ultérieure. Avant la publication d’ASP.NET Core dans .NET 6, le type string apparaissait sans la désignation de type nul (?).

FetchData (composant)

Cette section concerne l’application Client de la solution.

Le composant FetchData montre comment :

  • Provisionner un jeton d’accès.
  • Utiliser le jeton d’accès pour appeler une API de ressource protégée dans l’application serveur.

La directive @attribute [Authorize] indique au système d’autorisation Blazor WebAssembly que l’utilisateur doit être autorisé pour pouvoir visiter ce composant. La présence de l’attribut dans l’application Client n’empêche pas l’appel de l’API sur le serveur sans les informations d’identification appropriées. L’application Server doit également utiliser [Authorize] sur les points de terminaison appropriés pour les protéger correctement.

IAccessTokenProvider.RequestAccessToken se charge de demander un jeton d’accès qui peut être ajouté à la demande d’appel de l’API. Si le jeton est mis en cache ou si le service peut provisionner un nouveau jeton d’accès sans interaction de l’utilisateur, la demande de jeton aboutit. Sinon, la demande de jeton échoue avec un AccessTokenNotAvailableException, qui est intercepté dans une instruction try-catch.

Pour obtenir le jeton réel à inclure dans la demande, l’application doit vérifier la réussite de la demande en appelant tokenResult.TryGetToken(out var token).

Si la demande aboutit, la variable de jeton est renseignée avec le jeton d’accès. La propriété AccessToken.Value du jeton expose la chaîne littérale à inclure dans l’en-tête de demande Authorization.

Si la demande n’a pas abouti, car le jeton n’a pas pu être provisionné sans interaction utilisateur :

  • ASP.NET Core dans .NET 7 ou une version ultérieure : l’application accède à AccessTokenResult.InteractiveRequestUrl avec le AccessTokenResult.InteractionOptions donné pour permettre l’actualisation du jeton d’accès.
  • ASP.NET Core dans .NET 6 ou une version antérieure : le résultat du jeton contient une URL de redirection. La navigation vers cette URL permet à l’utilisateur d’accéder à la page de connexion et de revenir à la page active après une authentification réussie.
@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Azure App Service sur Linux

Spécifiez explicitement l’émetteur lors du déploiement sur Azure App Service sur Linux. Pour plus d’informations, consultez Utiliser Identity pour sécuriser un back-end d’API web pour les SPA.

Revendication de nom et de rôle avec autorisation d’API

Fabrique utilisateur personnalisée

Dans l’application Client, créez une fabrique utilisateur personnalisée. Le serveur Identity envoie plusieurs rôles en tant que JSgroupe ON dans une seule revendication role. Un seul rôle est envoyé en tant que valeur de chaîne dans la revendication. La fabrique crée une revendication role individuelle pour chacun des rôles de l’utilisateur.

CustomUserFactory.cs:

using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = 
                    account.AdditionalProperties[identity.RoleClaimType];

                if (options.RoleClaim is not null && rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            var roleValue = role.GetString();

                            if (!string.IsNullOrEmpty(roleValue))
                            {
                                identity.AddClaim(
                                  new Claim(options.RoleClaim, roleValue));
                            }

                        }
                    }
                    else
                    {
                        var roleValue = roles.GetString();

                        if (!string.IsNullOrEmpty(roleValue))
                        {
                            identity.AddClaim(
                              new Claim(options.RoleClaim, roleValue));
                        }
                    }
                }
            }
        }

        return user;
    }
}
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = account.AdditionalProperties[identity.RoleClaimType];

                if (rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                        }
                    }
                    else
                    {
                        identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
                    }
                }
            }
        }

        return user;
    }
}

Dans l'application Client, enregistrez l'usine dans le fichier Program :

builder.Services.AddApiAuthorization()
    .AddAccountClaimsPrincipalFactory<CustomUserFactory>();

Dans l’application Server, appelez AddRoles sur le générateur de Identity, qui ajoute des services liés aux rôles.

Dans le fichier Program :

using Microsoft.AspNetCore.Identity;

...

builder.Services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Dans Startup.cs :

using Microsoft.AspNetCore.Identity;

...

services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Configurez le serveur Identity

Utilisez l’une des approches suivantes :

Options d’autorisation d’API

Dans l’application Server :

  • Configurez le serveur Identity pour placer les revendications name et role dans le jeton d’ID et le jeton d’accès.
  • Empêchez le mappage par défaut pour les rôles dans le gestionnaire de jetons JWT.

Dans le fichier Program :

using System.IdentityModel.Tokens.Jwt;

...

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Dans Startup.cs :

using System.IdentityModel.Tokens.Jwt;
using System.Linq;

...

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Service de profil

Dans l’application Server, créez une ProfileService implémentation.

ProfileService.cs:

using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}
using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using System.Threading.Tasks;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}

Dans l'application Server, enregistrez le service de profil dans le fichier Program :

using Duende.IdentityServer.Services;

...

builder.Services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Dans l’application Server, inscrivez le service de profil dans Startup.ConfigureServices de Startup.cs :

using IdentityServer4.Services;

...

services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Utiliser des mécanismes d’autorisation

Dans l’application Client, les approches d’autorisation des composants sont fonctionnelles à ce stade. L’un des mécanismes d’autorisation dans les composants peut utiliser un rôle pour autoriser l’utilisateur :

User.Identity.Name est renseigné dans l’application Client avec le nom d’utilisateur de l’utilisateur, qui est généralement son adresse e-mail de connexion.

UserManager et SignInManager

Définissez le type de revendication d’identificateur d’utilisateur lorsqu’une application serveur nécessite :

Dans Program.cs ASP.NET Core dans .NET 6 ou version ultérieure :

using System.Security.Claims;

...

builder.Services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

Dans Startup.ConfigureServices pour les versions de ASP.NET Core antérieures à la version 6.0 :

using System.Security.Claims;

...

services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

Les journaux WeatherForecastController suivants consignent UserName quand la méthode Get est appelée.

Remarque

L’exemple suivant utilise un espace de noms de portée fichier, qui est une fonctionnalité C# 10 ou ultérieure (.NET 6 ou ultérieure).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using BlazorSample.Server.Models;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers;

[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly UserManager<ApplicationUser> userManager;

    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", 
        "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger, 
        UserManager<ApplicationUser> userManager)
    {
        this.logger = logger;
        this.userManager = userManager;
    }

    [HttpGet]
    public async Task<IEnumerable<WeatherForecast>> Get()
    {
        var rng = new Random();

        var user = await userManager.GetUserAsync(User);

        if (user != null)
        {
            logger.LogInformation("User.Identity.Name: {UserIdentityName}", user.UserName);
        }

        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
}

Dans l'exemple précédent :

  • L’espace de noms du projet Server est BlazorSample.Server.
  • L’espace de noms du projet Shared est BlazorSample.Shared.

Héberger dans Azure App Service avec un domaine et un certificat personnalisés

Les instructions suivantes expliquent :

  • Comment déployer une application Blazor WebAssembly hébergée avec le serveur Identity pour Azure App Service avec un domaine personnalisé.
  • Comment créer et utiliser un certificat TLS pour la communication de protocole HTTPS avec les navigateurs. Bien que les instructions se concentrent sur l’utilisation du certificat avec un domaine personnalisé, elles s’appliquent également à l’utilisation d’un domaine Azure Apps par défaut, par exemple contoso.azurewebsites.net.

Pour ce scénario d’hébergement, n’utilisez pas le même certificat pour Identity la clé de signature de jeton du serveur et la communication sécurisée HTTPS du site avec les navigateurs :

  • L’utilisation de certificats différents pour ces deux exigences est une bonne pratique de sécurité, car elle isole les clés privées à chaque usage.
  • Les certificats TLS pour la communication avec les navigateurs sont gérés indépendamment sans affecter la signature de jeton du serveur Identity.
  • Quand Azure Key Vault fournit un certificat à une application App Service pour une liaison de domaine personnalisée, le serveur Identity ne peut pas obtenir le même certificat auprès d’Azure Key Vault pour la signature de jeton. Bien que la configuration du serveur Identity pour utiliser le même certificat TLS à partir d’un chemin d’accès physique soit possible, le placement de certificats de sécurité dans le contrôle de code source est une mauvaise pratique qui doit être évitée dans la plupart des scénarios.

Dans les instructions suivantes, un certificat auto-signé est créé dans Azure Key Vault uniquement pour la signature de jeton de serveur Identity. La configuration du serveur Identity utilise le certificat du coffre de clés via le magasin de certificats de l’application >CurrentUserMy. D’autres certificats utilisés pour le trafic HTTPS avec des domaines personnalisés sont créés et configurés séparément du certificat de signature de serveur Identity.

Pour configurer une application, Azure App Service et Azure Key Vault à héberger avec un domaine personnalisé et HTTPS :

  1. Créez un plan App Service avec un niveau de plan supérieur ou égal à Basic B1. App Service nécessite un niveau de service Basic B1 ou supérieur pour utiliser des domaines personnalisés.

  2. Créez un certificat PFX pour la communication de navigateur sécurisée du site (protocole HTTPS) avec un nom commun du nom de domaine complet du site (FQDN) que votre organisation contrôle (par exemple, www.contoso.com). Créez le certificat avec :

    • Utilisations de clés
      • Validation de signature numérique (digitalSignature)
      • Chiffrage de clés (keyEncipherment)
    • Utilisations de clés améliorées/étendues
      • Authentification du client (1.3.6.1.5.5.7.3.2)
      • Authentification du serveur (1.3.6.1.5.5.7.3.1)

    Pour créer le certificat, utilisez l’une des approches suivantes ou tout autre outil ou service en ligne approprié :

    Notez le mot de passe, qui sera utilisé ultérieurement pour importer le certificat dans Azure Key Vault.

    Pour plus d’informations sur les certificats Azure Key Vault, consultez Azure Key Vault : Certificats.

  3. Créez un Azure Key Vault ou utilisez un coffre de clés existant dans votre abonnement Azure.

  4. Dans la zone Certificats du coffre de clés, importez le certificat de site PFX. Enregistrez l’empreinte numérique du certificat, qui sera utilisée ultérieurement dans la configuration de l’application.

  5. Dans Azure Key Vault, générez un nouveau certificat auto-signé pour la signature de jeton de serveur Identity. Donnez au certificat un nom de certificat et un objet. L’objet est spécifié en tant que CN={COMMON NAME}, où l’espace {COMMON NAME} réservé est le nom commun du certificat. Le nom commun peut être n’importe quelle chaîne alphanumérique. Par exemple, CN=IdentityServerSigning est un objet de certificat valide. Dans Configuration avancée de>la stratégie d’émission, utilisez les paramètres par défaut. Enregistrez l’empreinte numérique du certificat, qui sera utilisée ultérieurement dans la configuration de l’application.

  6. Accédez à Azure App Service dans le Portail Azure et créez un App Service avec la configuration suivante :

    • Publier défini sur Code.
    • Pile d’exécution définie sur le runtime de l’application.
    • Pour la référence SKU et la taille, vérifiez que le niveau App Service est Basic B1 supérieur ou supérieur. App Service nécessite un niveau de service Basic B1 ou supérieur pour utiliser des domaines personnalisés.
  7. Une fois qu’Azure a créé le App Service, ouvrez la configuration de l’application et ajoutez un nouveau paramètre d’application spécifiant les empreintes de certificat enregistrées précédemment. La clé de paramètre de l’application est WEBSITE_LOAD_CERTIFICATES. Séparez les empreintes du certificat dans la valeur de paramètre d’application par une virgule, comme le montre l’exemple suivant :

    • Clé :WEBSITE_LOAD_CERTIFICATES
    • Valeur: 57443A552A46DB...D55E28D412B943565,29F43A772CB6AF...1D04F0C67F85FB0B1

    Dans le Portail Azure, l’enregistrement des paramètres d’application est un processus en deux étapes : enregistrez le paramètre clé-valeur WEBSITE_LOAD_CERTIFICATES, puis sélectionnez le bouton Enregistrer en haut du panneau.

  8. Sélectionnez les paramètres TLS/SSL de l’application. Sélectionnez Certificats de clé privée (.pfx). Utilisez le processus Importer le certificat Key Vault. Utilisez le processus deux fois pour importer à la fois le certificat du site pour la communication HTTPS et le certificat de signature de jeton du serveur Identity auto-signé du site.

  9. Accédez au panneau Domaines personnalisés. Sur le site web de votre bureau d’enregistrement de domaines, utilisez l’adresse IP et l’ID de vérification Custom Domain pour configurer le domaine. Une configuration de domaine classique comprend :

    • Un enregistrement A avec un hôte de @ et une valeur de l’adresse IP à partir du Portail Azure.
    • Un enregistrement TXT avec un hôte de asuid et la valeur de l’ID de vérification généré par Azure et fourni par le Portail Azure.

    Veillez à enregistrer correctement les modifications sur le site web de votre bureau d’enregistrement de domaines. Certains sites web de bureau d’enregistrement nécessitent un processus en deux étapes pour enregistrer les enregistrements de domaine : un ou plusieurs enregistrements sont enregistrés individuellement, puis mettent à jour l’inscription du domaine avec un bouton distinct.

  10. Revenez au panneau Domaines personnalisés dans le Portail Azure. Sélectionnez Ajouter un domaine personnalisé. Sélectionnez l’option Enregistrement A. Indiquez le domaine et sélectionnez Valider. Si les enregistrements de domaine sont corrects et propagés sur Internet, le portail vous permet de sélectionner le bouton Ajouter un domaine personnalisé.

    La propagation des modifications d’inscription de domaine sur les serveurs DNS (Internet Domain Name Server) après leur traitement par votre bureau d’enregistrement de domaines peut prendre quelques jours. Si les enregistrements de domaine ne sont pas mis à jour dans un délai de trois jours ouvrables, vérifiez que les enregistrements sont correctement définis auprès du bureau d’enregistrement de domaines et contactez son support technique.

  11. Dans le panneau Domaines personnalisés, l’ÉTAT SSL du domaine est marqué Not Secure. Sélectionnez le lien Ajouter une liaison. Sélectionnez le certificat HTTPS de site dans le coffre de clés pour la liaison de domaine personnalisé.

  12. Dans Visual Studio, ouvrez le fichier de paramètres d’application du projet serveur (appsettings.json ou appsettings.Production.json). Dans configuration du serveur Identity, ajoutez la section suivante Key. Spécifiez l’objet du certificat auto-signé pour la clé Name. Dans l’exemple suivant, le nom commun du certificat attribué dans le coffre de clés est IdentityServerSigning, ce qui génère un Objet de CN=IdentityServerSigning :

    "IdentityServer": {
    
      ...
    
      "Key": {
        "Type": "Store",
        "StoreName": "My",
        "StoreLocation": "CurrentUser",
        "Name": "CN=IdentityServerSigning"
      }
    },
    
  13. Dans Visual Studio, créez un profil de publication Azure App Service pour le projet Serveur. Dans la barre de menus, sélectionnez : Générer>Publier>Nouveau>Azure>Azure App Service (Windows ou Linux). Lorsque Visual Studio est connecté à un abonnement Azure, vous pouvez définir l’affichage des ressources Azure par type de ressource. Accédez à la liste Application web pour rechercher le App Service de l’application et sélectionnez-le. Sélectionnez Terminer.

  14. Lorsque Visual Studio retourne à la fenêtre Publier, les dépendances du coffre de clés et SQL Server service de base de données sont automatiquement détectées.

    Aucune modification de la configuration des paramètres par défaut n’est requise pour le service de coffre de clés.

    À des fins de test, la base de données SQLite locale d’une application, qui est configurée par défaut par le modèle Blazor, peut être déployée avec l’application sans configuration supplémentaire. La configuration d’une autre base de données pour serveur Identity en production dépasse le cadre de cet article. Pour plus d’informations, consultez les ressources de base de données dans les ensembles de documentation suivants :

  15. Sélectionnez le lien Modifier sous le nom du profil de déploiement en haut de la fenêtre. Remplacez l’URL de destination par l’URL de domaine personnalisé du site (par exemple, https://www.contoso.com). Enregistrez les paramètres.

  16. Publier l'application Visual Studio ouvre une fenêtre de navigateur et demande le site à son domaine personnalisé.

La documentation Azure contient des détails supplémentaires sur l’utilisation des services Azure et des domaines personnalisés avec une liaison TLS dans App Service, notamment des informations sur l’utilisation des enregistrements CNAME au lieu des enregistrements A. Pour plus d’informations, consultez les ressources suivantes :

Nous vous recommandons d’utiliser une nouvelle fenêtre de navigateur en mode privé (par exemple, mode InPrivate Microsoft Edge ou mode Incognito Google Chrome) pour chaque exécution de test d’application après une modification apportée à l’application, à la configuration de l’application ou aux services Azure dans le Portail Azure. La persistance des cookies d’une exécution de test précédente peut entraîner un échec d’authentification ou d’autorisation lors du test du site, même lorsque la configuration du site est correcte. Pour obtenir plus d’informations sur la configuration de Visual Studio pour ouvrir une nouvelle fenêtre de navigateur privée pour chaque série de tests, consultez la section des Cookie et des données de site.

Lorsque App Service configuration est modifiée dans le Portail Azure, les mises à jour prennent généralement effet rapidement, mais ne sont pas instantanées. Parfois, vous devez attendre un court laps de temps pour qu’une App Service redémarre pour qu’une modification de la configuration prenne effet.

Si vous résolvez un problème de chargement de certificat de signature de clé de serveur Identity, exécutez la commande suivante dans un interpréteur de commandes Portail Azure Kudu PowerShell. La commande fournit la liste des certificats auxquels l’application peut accéder à partir du magasin de certificats CurrentUser>My. La sortie inclut des sujets de certificat et des empreintes numériques utiles lors du débogage d’une application :

Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList

Résoudre des problèmes

Logging

Pour activer la journalisation de débogage ou de trace pour l’authentification Blazor WebAssembly, consultez la section Journalisation d’authentification côté client de la Journalisation Blazor ASP.NET Core avec le sélecteur de version d’article défini sur ASP.NET Core 7.0 ou une version ultérieure.

Erreurs courantes

  • Mauvaise configuration de l’application ou du fournisseur d’identité (Identity Provider)

    Les erreurs les plus courantes sont provoquées par une configuration incorrecte. Voici quelques exemples :

    • Selon les besoins du scénario, une autorité, une instance, un ID de locataire, un domaine de locataire, un ID client ou un URI de redirection manquant ou incorrect empêche une application d’authentifier les clients.
    • Les étendues de requêtes erronées empêchent les clients d’accéder aux points de terminaison d’API web du serveur.
    • Des autorisations d’API serveur incorrectes ou manquantes empêchent les clients d’accéder aux points de terminaison d’API web du serveur.
    • Exécution de l’application sur un autre port que celui configuré dans l’URI de redirection de l’inscription d’application de l’IP. Notez qu’un port n’est pas requis pour Microsoft Entra ID et pour une application s’exécutant à une adresse de test de développement localhost. Cependant, la configuration du port de l’application et le port où l’application s’exécute doivent correspondre pour les adresses non-localhost.

    Les sections de configuration présentes dans les conseils d’aide de cet article montrent des exemples de configuration appropriée. Examinez attentivement chaque section de l’article à la recherche d’une mauvaise configuration de l’application et du fournisseur d’identité.

    Si la configuration semble correcte :

    • Analysez les journaux des applications.

    • Examinez le trafic réseau entre l’application cliente et le fournisseur d’identité ou l’application serveur à l’aide des outils de développement du navigateur. Bien souvent, un message d’erreur exact ou un message indiquant la cause du problème est retourné au client par le fournisseur d’identité ou l’application serveur, une fois qu’une demande a été effectuée. Vous trouverez des conseils d’aide sur les outils de développement dans les articles suivants :

    • Pour les versions de Blazor où un jeton JSON Web Token (JWT) est utilisé, décodez le contenu du jeton utilisé pour authentifier un client ou pour accéder à une API web de serveur, selon là où le problème se produit. Pour plus d’informations, consultez Inspecter le contenu d’un jeton JWT (JSON Web Token).

    L’équipe de documentation peut répondre aux commentaires et bogues relatifs aux articles (ouvrez un problème à partir de la section de commentaires de cette page). Toutefois, elle ne peut pas fournir de support produit. 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 (exploitable par des attaquants) dans le produit, consultez Signaler des problèmes de sécurité et des bogues (référentiel GitHub dotnet/aspnetcore).

  • Client non autorisé pour ME-ID

    Info : Échec de l’autorisation Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]. Ces conditions n’ont pas été remplies : DenyAnonymousAuthorizationRequirement : Nécessite un utilisateur authentifié.

    Erreur de rappel de la connexion à partir de ME-ID :

    • Erreur : unauthorized_client
    • Description : AADB2C90058: The provided application is not configured to allow public clients.

    Pour résoudre l’erreur :

    1. Dans le portail Azure, accédez au manifeste de l’application.
    2. Affectez à l’attribut allowPublicClient la valeur null ou true.

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 du fournisseur 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.

Exécuter l’application Server

Quand vous testez et résolvez les problèmes liés à une solutionBlazor WebAssembly hébergée, vérifiez que vous exécutez l’application à partir du projet Server.

Inspecter l’utilisateur

Le composant User suivant peut être utilisé directement dans les applications, ou servir de base à une personnalisation supplémentaire.

User.razor:

@page "/user"
@attribute [Authorize]
@using System.Text.Json
@using System.Security.Claims
@inject IAccessTokenProvider AuthorizationService

<h1>@AuthenticatedUser?.Identity?.Name</h1>

<h2>Claims</h2>

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

<h2>Access token</h2>

<p id="access-token">@AccessToken?.Value</p>

<h2>Access token claims</h2>

@foreach (var claim in GetAccessTokenClaims())
{
    <p>@(claim.Key): @claim.Value.ToString()</p>
}

@if (AccessToken != null)
{
    <h2>Access token expires</h2>

    <p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p>
    <p id="access-token-expires">@AccessToken.Expires</p>

    <h2>Access token granted scopes (as reported by the API)</h2>

    @foreach (var scope in AccessToken.GrantedScopes)
    {
        <p>Scope: @scope</p>
    }
}

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

    public ClaimsPrincipal AuthenticatedUser { get; set; }
    public AccessToken AccessToken { get; set; }

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        var state = await AuthenticationState;
        var accessTokenResult = await AuthorizationService.RequestAccessToken();

        if (!accessTokenResult.TryGetToken(out var token))
        {
            throw new InvalidOperationException(
                "Failed to provision the access token.");
        }

        AccessToken = token;

        AuthenticatedUser = state.User;
    }

    protected IDictionary<string, object> GetAccessTokenClaims()
    {
        if (AccessToken == null)
        {
            return new Dictionary<string, object>();
        }

        // header.payload.signature
        var payload = AccessToken.Value.Split(".")[1];
        var base64Payload = payload.Replace('-', '+').Replace('_', '/')
            .PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');

        return JsonSerializer.Deserialize<IDictionary<string, object>>(
            Convert.FromBase64String(base64Payload));
    }
}

Inspecter le contenu d’un jeton JWT (JSON Web Token)

Pour décoder un jeton JWT (JSON Web Token), utilisez l’outil jwt.ms de Microsoft. Les valeurs de l’IU ne quittent jamais votre navigateur.

Exemple de jeton JWT codé (raccourci pour des raisons d’affichage) :

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1j... bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzpD-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-yBMKV2_nXA25Q

Exemple JWT décodé par l’outil pour une application qui s’authentifie auprès d’Azure AAD B2C :

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
  "exp": 1610059429,
  "nbf": 1610055829,
  "ver": "1.0",
  "iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-226dcc9ad298/v2.0/",
  "sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
  "aud": "70bde375-fce3-4b82-984a-b247d823a03f",
  "nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
  "iat": 1610055829,
  "auth_time": 1610055822,
  "idp": "idp.com",
  "tfp": "B2C_1_signupsignin"
}.[Signature]

Ressources supplémentaires