Partager via


Authentification et autorisation avec ASP.NET Core Blazor Hybrid

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.

Important

Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.

Pour la version actuelle, consultez la version .NET 8 de cet article.

Cet article décrit la prise en charge d’ASP.NET Core pour la configuration et la gestion de la sécurité et d’ASP.NET Core Identity dans les applications Blazor Hybrid.

L’authentification dans les applications Blazor Hybrid est gérée par les bibliothèques de plateforme natives, qui offrent des garanties de sécurité renforcées que le bac à sable du navigateur ne peut pas offrir. L’authentification des applications natives utilise un mécanisme spécifique au système d’exploitation ou un protocole fédéré, tel qu’OpenID Connect (OIDC). Suivez les instructions du fournisseur d’identité que vous avez sélectionné pour l’application, puis poursuivez l’intégration de l’identité avec Blazor en suivant les instructions fournies dans cet article.

L’intégration de l’authentification doit atteindre les objectifs suivants pour les composants et les services Razor :

  • Utilisez les abstractions figurant dans le package Microsoft.AspNetCore.Components.Authorization, telles que AuthorizeView.
  • Réagissez aux modifications apportées au contexte d’authentification.
  • Accédez aux informations d’identification provisionnées par l’application à partir du fournisseur d’identité, telles que les jetons d’accès pour effectuer des appels d’API autorisés.

Une fois que l’authentification a été ajoutée à une application .NET MAUI, WPF ou Windows Forms, et que les utilisateurs sont en mesure de se connecter et de se déconnecter correctement, intégrez l’authentification à Blazor pour rendre l’utilisateur authentifié disponible pour les composants et les services Razor. Procédez comme suit :

  • Référencez le package Microsoft.AspNetCore.Components.Authorization.

    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.

  • Implémentez un AuthenticationStateProvider personnalisé, qui constitue l’abstraction que les composants Razor utilisent pour accéder aux informations relatives à l’utilisateur authentifié et pour recevoir des mises à jour lorsque l’état d’authentification change.

  • Inscrivez le fournisseur d’état d’authentification personnalisé dans le conteneur d’injection de dépendances.

Les applications .NET MAUI utilisent Xamarin.Essentials : Authentificateur web : La classe WebAuthenticator permet à l’application de lancer des flux d’authentification basés sur un navigateur qui sont à l’écoute d’un rappel vers une URL spécifique inscrite auprès de l’application.

Pour obtenir des conseils supplémentaires, consultez les ressources suivantes :

Les applications Windows Forms utilisent la plateforme d’identités Microsoft pour s’intégrer à Microsoft Entra (ME-ID) et AAD B2C. Pour plus d’informations, consultez Présentation de la bibliothèque d’authentification Microsoft (MSAL).

Créer un élément AuthenticationStateProvider personnalisé sans mises à jour de modification de l’utilisateur

Si l’application authentifie l’utilisateur immédiatement après le lancement de l’application et que l’utilisateur authentifié reste le même pour l’intégralité de la durée de vie de l’application, les notifications de modification de l’utilisateur ne sont pas requises et l’application fournit uniquement des informations sur l’utilisateur authentifié. Dans ce scénario, l’utilisateur se connecte à l’application quand celle-ci est ouverte, et l’application affiche à nouveau l’écran de connexion une fois l’utilisateur déconnecté. L’élément ExternalAuthStateProvider suivant est un exemple d’implémentation d’un élément AuthenticationStateProvider personnalisé pour ce scénario d’authentification.

Remarque

L’élément AuthenticationStateProvider personnalisé suivant ne déclare pas d’espace de noms pour que l’exemple de code s’applique à n’importe quelle application Blazor Hybrid. Toutefois, une bonne pratique consiste à fournir l’espace de noms de votre application quand vous implémentez l’exemple dans une application de production.

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private readonly Task<AuthenticationState> authenticationState;

    public ExternalAuthStateProvider(AuthenticatedUser user) => 
        authenticationState = Task.FromResult(new AuthenticationState(user.Principal));

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        authenticationState;
}

public class AuthenticatedUser
{
    public ClaimsPrincipal Principal { get; set; } = new();
}

Les étapes suivantes décrivent comment :

  • Ajouter les espaces de noms nécessaires.
  • Ajouter les services d’autorisation et les abstractions Blazor à la collection de services.
  • Générer la collection de services.
  • Résoudre le service AuthenticatedUser pour définir le principal de revendications de l’utilisateur authentifié. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.
  • Retourner l’hôte généré.

Dans la méthode MauiProgram.CreateMauiApp de MauiProgram.cs, ajoutez des espaces de noms pour Microsoft.AspNetCore.Components.Authorization et System.Security.Claims :

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

Supprimez la ligne de code suivante qui retourne un élémentMicrosoft.Maui.Hosting.MauiApp généré :

- return builder.Build();

Remplacez la ligne de code précédente par le code suivant. Ajoutez du code OpenID/MSAL pour authentifier l’utilisateur. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

Les étapes suivantes décrivent comment :

  • Ajouter les espaces de noms nécessaires.
  • Ajouter les services d’autorisation et les abstractions Blazor à la collection de services.
  • Générer la collection de services et ajouter la collection de services générée en tant que ressource à l’élément ResourceDictionary de l’application.
  • Résoudre le service AuthenticatedUser pour définir le principal de revendications de l’utilisateur authentifié. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.
  • Retourner l’hôte généré.

Dans le constructeur de MainWindow (MainWindow.xaml.cs), ajoutez des espaces de noms pour Microsoft.AspNetCore.Components.Authorization et System.Security.Claims :

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

Supprimez la ligne de code suivante qui ajoute la collection de services générée en tant que ressource à l’élément ResourceDictionary de l’application :

- Resources.Add("services", serviceCollection.BuildServiceProvider());

Remplacez la ligne de code précédente par le code suivant. Ajoutez du code OpenID/MSAL pour authentifier l’utilisateur. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);

var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

Les étapes suivantes décrivent comment :

  • Ajouter les espaces de noms nécessaires.
  • Ajouter les services d’autorisation et les abstractions Blazor à la collection de services.
  • Générez la collection de services et ajoutez la collection de services générée au fournisseur de services de l’application.
  • Résoudre le service AuthenticatedUser pour définir le principal de revendications de l’utilisateur authentifié. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.

Dans le constructeur de Form1 (Form1.cs), ajoutez des espaces de noms pour Microsoft.AspNetCore.Components.Authorization et System.Security.Claims :

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

Supprimez la ligne de code suivante qui définit la collection de services générée sur le fournisseur de services de l’application :

- blazorWebView1.Services = services.BuildServiceProvider();

Remplacez la ligne de code précédente par le code suivant. Ajoutez du code OpenID/MSAL pour authentifier l’utilisateur. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;

var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

Créer un élément AuthenticationStateProvider personnalisé avec des mises à jour de modification de l’utilisateur

Pour mettre à jour l’utilisateur pendant l’exécution de l’application Blazor, appelez NotifyAuthenticationStateChanged dans l’implémentation AuthenticationStateProvider en utilisant l’une des approches suivantes :

Signaler une mise à jour d’authentification en dehors de BlazorWebView (Option 1)

Un AuthenticationStateProvider personnalisé peut utiliser un service global pour signaler une mise à jour d’authentification. Nous recommandons que le service offre un événement auquel AuthenticationStateProvider pourra s’abonner, l’événement appelant NotifyAuthenticationStateChanged.

Remarque

L’élément AuthenticationStateProvider personnalisé suivant ne déclare pas d’espace de noms pour que l’exemple de code s’applique à n’importe quelle application Blazor Hybrid. Toutefois, une bonne pratique consiste à fournir l’espace de noms de votre application quand vous implémentez l’exemple dans une application de production.

ExternalAuthStateProvider.cs:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private AuthenticationState currentUser;

    public ExternalAuthStateProvider(ExternalAuthService service)
    {
        currentUser = new AuthenticationState(service.CurrentUser);

        service.UserChanged += (newUser) =>
        {
            currentUser = new AuthenticationState(newUser);
            NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
        };
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(currentUser);
}

public class ExternalAuthService
{
    public event Action<ClaimsPrincipal>? UserChanged;
    private ClaimsPrincipal? currentUser;

    public ClaimsPrincipal CurrentUser
    {
        get { return currentUser ?? new(); }
        set
        {
            currentUser = value;

            if (UserChanged is not null)
            {
                UserChanged(currentUser);
            }
        }
    }
}

Dans la méthode MauiProgram.CreateMauiApp de MauiProgram.cs, ajoutez un espace de noms pour Microsoft.AspNetCore.Components.Authorization :

using Microsoft.AspNetCore.Components.Authorization;

Ajoutez les services d’autorisation et les abstractions Blazor à la collection de services :

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

Dans le constructeur de MainWindow (MainWindow.xaml.cs), ajoutez un espace de noms pour Microsoft.AspNetCore.Components.Authorization :

using Microsoft.AspNetCore.Components.Authorization;

Ajoutez les services d’autorisation et les abstractions Blazor à la collection de services :

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();

Dans le constructeur de Form1 (Form1.cs), ajoutez un espace de noms pour Microsoft.AspNetCore.Components.Authorization :

using Microsoft.AspNetCore.Components.Authorization;

Ajoutez les services d’autorisation et les abstractions Blazor à la collection de services :

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();

Où que l’application authentifie un utilisateur, résolvez le service ExternalAuthService :

var authService = host.Services.GetRequiredService<ExternalAuthService>();

Exécutez votre code OpenID/MSAL personnalisé pour authentifier l’utilisateur. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités. L’utilisateur authentifié (authenticatedUser dans l’exemple suivant) est un nouveau ClaimsPrincipal basé sur un nouvel élément ClaimsIdentity.

Définissez l’utilisateur actuel sur l’utilisateur authentifié :

authService.CurrentUser = authenticatedUser;

Une alternative à l’approche précédente consiste à définir le principal de l’utilisateur sur System.Threading.Thread.CurrentPrincipal au lieu de le définir via un service, ce qui évite d’utiliser le conteneur d’injection de dépendances :

public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(
            new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ?? 
                new ClaimsPrincipal(new ClaimsIdentity())));
}

Avec l’approche alternative, seuls les services d’autorisation (AddAuthorizationCore) et CurrentThreadUserAuthenticationStateProvider (.TryAddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()) sont ajoutés à la collection de services.

Gérer l’authentification dans BlazorWebView (Option 2)

Un AuthenticationStateProvider personnalisé peut inclure des méthodes supplémentaires pour déclencher la connexion et la déconnexion et mettre à jour l’utilisateur.

Remarque

L’élément AuthenticationStateProvider personnalisé suivant ne déclare pas d’espace de noms pour que l’exemple de code s’applique à n’importe quelle application Blazor Hybrid. Toutefois, une bonne pratique consiste à fournir l’espace de noms de votre application quand vous implémentez l’exemple dans une application de production.

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(new AuthenticationState(currentUser));

    public Task LogInAsync()
    {
        var loginTask = LogInAsyncCore();
        NotifyAuthenticationStateChanged(loginTask);

        return loginTask;

        async Task<AuthenticationState> LogInAsyncCore()
        {
            var user = await LoginWithExternalProviderAsync();
            currentUser = user;

            return new AuthenticationState(currentUser);
        }
    }

    private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
    {
        /*
            Provide OpenID/MSAL code to authenticate the user. See your identity 
            provider's documentation for details.

            Return a new ClaimsPrincipal based on a new ClaimsIdentity.
        */
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

        return Task.FromResult(authenticatedUser);
    }

    public void Logout()
    {
        currentUser = new ClaimsPrincipal(new ClaimsIdentity());
        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(currentUser)));
    }
}

Dans l'exemple précédent :

  • L’appel à LogInAsyncCore déclenche le processus de connexion.
  • L’appel à NotifyAuthenticationStateChanged notifie qu’une mise à jour est en cours, ce qui permet à l’application de fournir une interface utilisateur temporaire pendant le processus de connexion ou de déconnexion.
  • Le retour de loginTask retourne la tâche afin que le composant qui a déclenché la connexion puisse attendre et réagir une fois la tâche terminée.
  • La méthode LoginWithExternalProviderAsync est implémentée par le développeur pour connecter l’utilisateur à l’aide du kit SDK du fournisseur d’identité. Pour plus d’informations, consultez la documentation de votre fournisseur d’identité. L’utilisateur authentifié (authenticatedUser) est un nouveau ClaimsPrincipal basé sur un nouvel élément ClaimsIdentity.

Dans la méthode MauiProgram.CreateMauiApp de MauiProgram.cs, ajoutez les services d’autorisation et l’abstraction Blazor à la collection de services :

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Dans le constructeur de MainWindow (MainWindow.xaml.cs), ajoutez les services d’autorisation et l’abstraction Blazor à la collection de services :

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Dans le constructeur de Form1 (Form1.cs), ajoutez les services d’autorisation et l’abstraction Blazor à la collection de services :

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Le composant LoginComponent suivant illustre comment connecter un utilisateur. Dans une application classique, le composant LoginComponent est affiché uniquement dans un composant parent si l’utilisateur n’est pas connecté à l’application.

Shared/LoginComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
    public async Task Login()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .LogInAsync();
    }
}

Le composant LogoutComponent suivant illustre comment déconnecter un utilisateur. Dans une application classique, le composant LogoutComponent est affiché uniquement dans un composant parent si l’utilisateur est connecté à l’application.

Shared/LogoutComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
    public async Task Logout()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .Logout();
    }
}

Accès à d’autres informations d’authentification

Blazor ne définit pas d’abstraction pour gérer d’autres informations d’identification, telles que les jetons d’accès à utiliser pour les requêtes HTTP adressées aux API web. Nous vous recommandons de suivre les instructions du fournisseur d’identité pour gérer les informations d’identification de l’utilisateur avec les primitives fournies par le kit SDK du fournisseur d’identité.

Il est courant que les kits SDK du fournisseur d’identité utilisent un magasin de jetons pour les informations d’identification d’utilisateur stockées dans l’appareil. Si la primitive du magasin de jetons du kit SDK est ajoutée au conteneur de service, consommez la primitive du kit SDK dans l’application.

Le framework Blazor ne connaît pas les informations d’authentification d’un utilisateur et n’interagit en aucune manière avec ces informations, de sorte que le code de l’application est libre de suivre l’approche que vous jugez la plus pratique. Toutefois, suivez les instructions générales de sécurité fournies dans la section suivante, Autres considérations relatives à la sécurité de l’authentification, quand vous implémentez du code d’authentification dans une application.

Autres considérations relatives à la sécurité de l’authentification

Le processus d’authentification est externe à Blazor et nous recommandons aux développeurs d’accéder aux instructions du fournisseur d’identité pour obtenir des conseils de sécurité supplémentaires.

Lors de l’implémentation de l’authentification :

  • Évitez l’authentification dans le contexte du Web View. Par exemple, évitez d’utiliser une bibliothèque JavaScript OAuth pour effectuer le flux d’authentification. Dans une application monopage, les jetons d’authentification ne sont pas masqués dans JavaScript et peuvent être facilement découverts par des utilisateurs malveillants et utilisés à des fins nuisibles. Les applications natives ne courent pas ce risque, car elles ne peuvent obtenir que des jetons en dehors du contexte du navigateur, ce qui signifie que les scripts tiers non autorisés ne peuvent pas voler les jetons et compromettre l’application.
  • Évitez d’implémenter vous-même le workflow d’authentification. Dans la plupart des cas, les bibliothèques de plateforme gèrent en toute sécurité le workflow d’authentification, en utilisant le navigateur du système au lieu d’utiliser un élément Web View personnalisé pouvant être détourné.
  • Évitez d’utiliser le contrôle Web View de la plateforme pour effectuer l’authentification. Au lieu de cela, utilisez le navigateur du système si possible.
  • Évitez de transmettre les jetons au contexte de document (JavaScript). Dans certains cas, une bibliothèque JavaScript dans le document est nécessaire pour effectuer un appel autorisé à un service externe. Au lieu de rendre le jeton disponible pour JavaScript via l’interopérabilité JS :
    • Fournissez un jeton temporaire généré à la bibliothèque et dans le Web View.
    • Interceptez la demande réseau sortante dans le code.
    • Remplacez le jeton temporaire par le jeton réel et vérifiez que la destination de la demande est valide.

Ressources supplémentaires

L’authentification dans les applications Blazor Hybrid est gérée par les bibliothèques de plateforme natives, qui offrent des garanties de sécurité renforcées que le bac à sable du navigateur ne peut pas offrir. L’authentification des applications natives utilise un mécanisme spécifique au système d’exploitation ou un protocole fédéré, tel qu’OpenID Connect (OIDC). Suivez les instructions du fournisseur d’identité que vous avez sélectionné pour l’application, puis poursuivez l’intégration de l’identité avec Blazor en suivant les instructions fournies dans cet article.

L’intégration de l’authentification doit atteindre les objectifs suivants pour les composants et les services Razor :

  • Utilisez les abstractions figurant dans le package Microsoft.AspNetCore.Components.Authorization, telles que AuthorizeView.
  • Réagissez aux modifications apportées au contexte d’authentification.
  • Accédez aux informations d’identification provisionnées par l’application à partir du fournisseur d’identité, telles que les jetons d’accès pour effectuer des appels d’API autorisés.

Une fois que l’authentification a été ajoutée à une application .NET MAUI, WPF ou Windows Forms, et que les utilisateurs sont en mesure de se connecter et de se déconnecter correctement, intégrez l’authentification à Blazor pour rendre l’utilisateur authentifié disponible pour les composants et les services Razor. Procédez comme suit :

  • Référencez le package Microsoft.AspNetCore.Components.Authorization.

    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.

  • Implémentez un AuthenticationStateProvider personnalisé, qui constitue l’abstraction que les composants Razor utilisent pour accéder aux informations relatives à l’utilisateur authentifié et pour recevoir des mises à jour lorsque l’état d’authentification change.

  • Inscrivez le fournisseur d’état d’authentification personnalisé dans le conteneur d’injection de dépendances.

Les applications .NET MAUI utilisent Xamarin.Essentials : Authentificateur web : La classe WebAuthenticator permet à l’application de lancer des flux d’authentification basés sur un navigateur qui sont à l’écoute d’un rappel vers une URL spécifique inscrite auprès de l’application.

Pour obtenir des conseils supplémentaires, consultez les ressources suivantes :

Les applications Windows Forms utilisent la plateforme d’identités Microsoft pour s’intégrer à Microsoft Entra (ME-ID) et AAD B2C. Pour plus d’informations, consultez Présentation de la bibliothèque d’authentification Microsoft (MSAL).

Créer un élément AuthenticationStateProvider personnalisé sans mises à jour de modification de l’utilisateur

Si l’application authentifie l’utilisateur immédiatement après le lancement de l’application et que l’utilisateur authentifié reste le même pour l’intégralité de la durée de vie de l’application, les notifications de modification de l’utilisateur ne sont pas requises et l’application fournit uniquement des informations sur l’utilisateur authentifié. Dans ce scénario, l’utilisateur se connecte à l’application quand celle-ci est ouverte, et l’application affiche à nouveau l’écran de connexion une fois l’utilisateur déconnecté. L’élément ExternalAuthStateProvider suivant est un exemple d’implémentation d’un élément AuthenticationStateProvider personnalisé pour ce scénario d’authentification.

Remarque

L’élément AuthenticationStateProvider personnalisé suivant ne déclare pas d’espace de noms pour que l’exemple de code s’applique à n’importe quelle application Blazor Hybrid. Toutefois, une bonne pratique consiste à fournir l’espace de noms de votre application quand vous implémentez l’exemple dans une application de production.

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private readonly Task<AuthenticationState> authenticationState;

    public ExternalAuthStateProvider(AuthenticatedUser user) => 
        authenticationState = Task.FromResult(new AuthenticationState(user.Principal));

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        authenticationState;
}

public class AuthenticatedUser
{
    public ClaimsPrincipal Principal { get; set; } = new();
}

Les étapes suivantes décrivent comment :

  • Ajouter les espaces de noms nécessaires.
  • Ajouter les services d’autorisation et les abstractions Blazor à la collection de services.
  • Générer la collection de services.
  • Résoudre le service AuthenticatedUser pour définir le principal de revendications de l’utilisateur authentifié. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.
  • Retourner l’hôte généré.

Dans la méthode MauiProgram.CreateMauiApp de MauiProgram.cs, ajoutez des espaces de noms pour Microsoft.AspNetCore.Components.Authorization et System.Security.Claims :

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

Supprimez la ligne de code suivante qui retourne un élémentMicrosoft.Maui.Hosting.MauiApp généré :

- return builder.Build();

Remplacez la ligne de code précédente par le code suivant. Ajoutez du code OpenID/MSAL pour authentifier l’utilisateur. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

Les étapes suivantes décrivent comment :

  • Ajouter les espaces de noms nécessaires.
  • Ajouter les services d’autorisation et les abstractions Blazor à la collection de services.
  • Générer la collection de services et ajouter la collection de services générée en tant que ressource à l’élément ResourceDictionary de l’application.
  • Résoudre le service AuthenticatedUser pour définir le principal de revendications de l’utilisateur authentifié. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.
  • Retourner l’hôte généré.

Dans le constructeur de MainWindow (MainWindow.xaml.cs), ajoutez des espaces de noms pour Microsoft.AspNetCore.Components.Authorization et System.Security.Claims :

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

Supprimez la ligne de code suivante qui ajoute la collection de services générée en tant que ressource à l’élément ResourceDictionary de l’application :

- Resources.Add("services", serviceCollection.BuildServiceProvider());

Remplacez la ligne de code précédente par le code suivant. Ajoutez du code OpenID/MSAL pour authentifier l’utilisateur. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);

var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

Les étapes suivantes décrivent comment :

  • Ajouter les espaces de noms nécessaires.
  • Ajouter les services d’autorisation et les abstractions Blazor à la collection de services.
  • Générez la collection de services et ajoutez la collection de services générée au fournisseur de services de l’application.
  • Résoudre le service AuthenticatedUser pour définir le principal de revendications de l’utilisateur authentifié. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.

Dans le constructeur de Form1 (Form1.cs), ajoutez des espaces de noms pour Microsoft.AspNetCore.Components.Authorization et System.Security.Claims :

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

Supprimez la ligne de code suivante qui définit la collection de services générée sur le fournisseur de services de l’application :

- blazorWebView1.Services = services.BuildServiceProvider();

Remplacez la ligne de code précédente par le code suivant. Ajoutez du code OpenID/MSAL pour authentifier l’utilisateur. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités.

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;

var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

Créer un élément AuthenticationStateProvider personnalisé avec des mises à jour de modification de l’utilisateur

Pour mettre à jour l’utilisateur pendant l’exécution de l’application Blazor, appelez NotifyAuthenticationStateChanged dans l’implémentation AuthenticationStateProvider en utilisant l’une des approches suivantes :

Signaler une mise à jour d’authentification en dehors de BlazorWebView (Option 1)

Un AuthenticationStateProvider personnalisé peut utiliser un service global pour signaler une mise à jour d’authentification. Nous recommandons que le service offre un événement auquel AuthenticationStateProvider pourra s’abonner, l’événement appelant NotifyAuthenticationStateChanged.

Remarque

L’élément AuthenticationStateProvider personnalisé suivant ne déclare pas d’espace de noms pour que l’exemple de code s’applique à n’importe quelle application Blazor Hybrid. Toutefois, une bonne pratique consiste à fournir l’espace de noms de votre application quand vous implémentez l’exemple dans une application de production.

ExternalAuthStateProvider.cs:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private AuthenticationState currentUser;

    public ExternalAuthStateProvider(ExternalAuthService service)
    {
        currentUser = new AuthenticationState(service.CurrentUser);

        service.UserChanged += (newUser) =>
        {
            currentUser = new AuthenticationState(newUser);
            NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
        };
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(currentUser);
}

public class ExternalAuthService
{
    public event Action<ClaimsPrincipal>? UserChanged;
    private ClaimsPrincipal? currentUser;

    public ClaimsPrincipal CurrentUser
    {
        get { return currentUser ?? new(); }
        set
        {
            currentUser = value;

            if (UserChanged is not null)
            {
                UserChanged(currentUser);
            }
        }
    }
}

Dans la méthode MauiProgram.CreateMauiApp de MauiProgram.cs, ajoutez un espace de noms pour Microsoft.AspNetCore.Components.Authorization :

using Microsoft.AspNetCore.Components.Authorization;

Ajoutez les services d’autorisation et les abstractions Blazor à la collection de services :

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

Dans le constructeur de MainWindow (MainWindow.xaml.cs), ajoutez un espace de noms pour Microsoft.AspNetCore.Components.Authorization :

using Microsoft.AspNetCore.Components.Authorization;

Ajoutez les services d’autorisation et les abstractions Blazor à la collection de services :

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();

Dans le constructeur de Form1 (Form1.cs), ajoutez un espace de noms pour Microsoft.AspNetCore.Components.Authorization :

using Microsoft.AspNetCore.Components.Authorization;

Ajoutez les services d’autorisation et les abstractions Blazor à la collection de services :

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();

Où que l’application authentifie un utilisateur, résolvez le service ExternalAuthService :

var authService = host.Services.GetRequiredService<ExternalAuthService>();

Exécutez votre code OpenID/MSAL personnalisé pour authentifier l’utilisateur. Pour plus d’informations, consultez la documentation de votre fournisseur d’identités. L’utilisateur authentifié (authenticatedUser dans l’exemple suivant) est un nouveau ClaimsPrincipal basé sur un nouvel élément ClaimsIdentity.

Définissez l’utilisateur actuel sur l’utilisateur authentifié :

authService.CurrentUser = authenticatedUser;

Une alternative à l’approche précédente consiste à définir le principal de l’utilisateur sur System.Threading.Thread.CurrentPrincipal au lieu de le définir via un service, ce qui évite d’utiliser le conteneur d’injection de dépendances :

public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(
            new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ?? 
                new ClaimsPrincipal(new ClaimsIdentity())));
}

Avec l’approche alternative, seuls les services d’autorisation (AddAuthorizationCore) et CurrentThreadUserAuthenticationStateProvider (.AddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()) sont ajoutés à la collection de services.

Gérer l’authentification dans BlazorWebView (Option 2)

Un AuthenticationStateProvider personnalisé peut inclure des méthodes supplémentaires pour déclencher la connexion et la déconnexion et mettre à jour l’utilisateur.

Remarque

L’élément AuthenticationStateProvider personnalisé suivant ne déclare pas d’espace de noms pour que l’exemple de code s’applique à n’importe quelle application Blazor Hybrid. Toutefois, une bonne pratique consiste à fournir l’espace de noms de votre application quand vous implémentez l’exemple dans une application de production.

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(new AuthenticationState(currentUser));

    public Task LogInAsync()
    {
        var loginTask = LogInAsyncCore();
        NotifyAuthenticationStateChanged(loginTask);

        return loginTask;

        async Task<AuthenticationState> LogInAsyncCore()
        {
            var user = await LoginWithExternalProviderAsync();
            currentUser = user;

            return new AuthenticationState(currentUser);
        }
    }

    private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
    {
        /*
            Provide OpenID/MSAL code to authenticate the user. See your identity 
            provider's documentation for details.

            Return a new ClaimsPrincipal based on a new ClaimsIdentity.
        */
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

        return Task.FromResult(authenticatedUser);
    }

    public void Logout()
    {
        currentUser = new ClaimsPrincipal(new ClaimsIdentity());
        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(currentUser)));
    }
}

Dans l'exemple précédent :

  • L’appel à LogInAsyncCore déclenche le processus de connexion.
  • L’appel à NotifyAuthenticationStateChanged notifie qu’une mise à jour est en cours, ce qui permet à l’application de fournir une interface utilisateur temporaire pendant le processus de connexion ou de déconnexion.
  • Le retour de loginTask retourne la tâche afin que le composant qui a déclenché la connexion puisse attendre et réagir une fois la tâche terminée.
  • La méthode LoginWithExternalProviderAsync est implémentée par le développeur pour connecter l’utilisateur à l’aide du kit SDK du fournisseur d’identité. Pour plus d’informations, consultez la documentation de votre fournisseur d’identité. L’utilisateur authentifié (authenticatedUser) est un nouveau ClaimsPrincipal basé sur un nouvel élément ClaimsIdentity.

Dans la méthode MauiProgram.CreateMauiApp de MauiProgram.cs, ajoutez les services d’autorisation et l’abstraction Blazor à la collection de services :

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Dans le constructeur de MainWindow (MainWindow.xaml.cs), ajoutez les services d’autorisation et l’abstraction Blazor à la collection de services :

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Dans le constructeur de Form1 (Form1.cs), ajoutez les services d’autorisation et l’abstraction Blazor à la collection de services :

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Le composant LoginComponent suivant illustre comment connecter un utilisateur. Dans une application classique, le composant LoginComponent est affiché uniquement dans un composant parent si l’utilisateur n’est pas connecté à l’application.

Shared/LoginComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
    public async Task Login()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .LogInAsync();
    }
}

Le composant LogoutComponent suivant illustre comment déconnecter un utilisateur. Dans une application classique, le composant LogoutComponent est affiché uniquement dans un composant parent si l’utilisateur est connecté à l’application.

Shared/LogoutComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
    public async Task Logout()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .Logout();
    }
}

Accès à d’autres informations d’authentification

Blazor ne définit pas d’abstraction pour gérer d’autres informations d’identification, telles que les jetons d’accès à utiliser pour les requêtes HTTP adressées aux API web. Nous vous recommandons de suivre les instructions du fournisseur d’identité pour gérer les informations d’identification de l’utilisateur avec les primitives fournies par le kit SDK du fournisseur d’identité.

Il est courant que les kits SDK du fournisseur d’identité utilisent un magasin de jetons pour les informations d’identification d’utilisateur stockées dans l’appareil. Si la primitive du magasin de jetons du kit SDK est ajoutée au conteneur de service, consommez la primitive du kit SDK dans l’application.

Le framework Blazor ne connaît pas les informations d’authentification d’un utilisateur et n’interagit en aucune manière avec ces informations, de sorte que le code de l’application est libre de suivre l’approche que vous jugez la plus pratique. Toutefois, suivez les instructions générales de sécurité fournies dans la section suivante, Autres considérations relatives à la sécurité de l’authentification, quand vous implémentez du code d’authentification dans une application.

Autres considérations relatives à la sécurité de l’authentification

Le processus d’authentification est externe à Blazor et nous recommandons aux développeurs d’accéder aux instructions du fournisseur d’identité pour obtenir des conseils de sécurité supplémentaires.

Lors de l’implémentation de l’authentification :

  • Évitez l’authentification dans le contexte du Web View. Par exemple, évitez d’utiliser une bibliothèque JavaScript OAuth pour effectuer le flux d’authentification. Dans une application monopage, les jetons d’authentification ne sont pas masqués dans JavaScript et peuvent être facilement découverts par des utilisateurs malveillants et utilisés à des fins nuisibles. Les applications natives ne courent pas ce risque, car elles ne peuvent obtenir que des jetons en dehors du contexte du navigateur, ce qui signifie que les scripts tiers non autorisés ne peuvent pas voler les jetons et compromettre l’application.
  • Évitez d’implémenter vous-même le workflow d’authentification. Dans la plupart des cas, les bibliothèques de plateforme gèrent en toute sécurité le workflow d’authentification, en utilisant le navigateur du système au lieu d’utiliser un élément Web View personnalisé pouvant être détourné.
  • Évitez d’utiliser le contrôle Web View de la plateforme pour effectuer l’authentification. Au lieu de cela, utilisez le navigateur du système si possible.
  • Évitez de transmettre les jetons au contexte de document (JavaScript). Dans certains cas, une bibliothèque JavaScript dans le document est nécessaire pour effectuer un appel autorisé à un service externe. Au lieu de rendre le jeton disponible pour JavaScript via l’interopérabilité JS :
    • Fournissez un jeton temporaire généré à la bibliothèque et dans le Web View.
    • Interceptez la demande réseau sortante dans le code.
    • Remplacez le jeton temporaire par le jeton réel et vérifiez que la destination de la demande est valide.

Ressources supplémentaires