Condividi tramite


Autenticazione e autorizzazione per ASP.NET Core Blazor Hybrid

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Questo articolo descrive il supporto di ASP.NET Core per la configurazione e la gestione della sicurezza e di ASP.NET Core Identity nelle app Blazor Hybrid.

L'autenticazione nelle app Blazor Hybrid viene gestita dalle librerie della piattaforma nativa perché offrono garanzie di sicurezza avanzata che la sandbox del browser non può offrire. L'autenticazione delle app native usa un meccanismo specifico del sistema operativo o un protocollo federato, ad esempio OpenID Connect (OIDC). Seguire le indicazioni per il identity provider selezionato per l'app e quindi integrarsi identity ulteriormente con Blazor l'uso delle indicazioni riportate in questo articolo.

L'integrazione dell'autenticazione deve raggiungere gli obiettivi seguenti per i componenti e i servizi di Razor:

  • Usare le astrazioni nel pacchetto Microsoft.AspNetCore.Components.Authorization, ad esempio AuthorizeView.
  • Reagire alle modifiche nel contesto di autenticazione.
  • Credenziali di accesso di cui viene effettuato il identity provisioning dall'app dal provider, ad esempio i token di accesso per eseguire chiamate API autorizzate.

Dopo che l'autenticazione è stata aggiunta a un'app .NET MAUI, WPF o Windows Form e gli utenti possono accedere e disconnettersi correttamente, integrare l'autenticazione con Blazor per rendere l'utente autenticato disponibile per i componenti e i servizi di Razor. Procedi come segue:

Le app .NET MAUI usano l'autenticatore Web di Xamarin.Essentials: la classe WebAuthenticator consente all'app di avviare i flussi di autenticazione basata sul browser che sono in ascolto di un callback a un URL specifico registrato con l'app.

Per altre indicazioni, vedere le risorse seguenti:

Windows Form le app usano Piattaforma Microsoft identity da integrare con Microsoft Entra (ME-ID) e AAD B2C. Per altre informazioni, vedere Panoramica di Microsoft Authentication Library (MSAL).

Creare una classe AuthenticationStateProvider personalizzata senza aggiornamenti delle modifiche utente

Se l'app autentica l'utente immediatamente dopo l'avvio dell'app e l'utente autenticato rimane lo stesso per tutta la durata dell'app, le notifiche di modifica utente non sono necessarie e l'app fornisce solo informazioni sull'utente autenticato. In questo scenario l'utente accede all'app quando l'app viene aperta e l'app visualizza nuovamente la schermata di accesso dopo la disconnessione dell'utente. L'elemento ExternalAuthStateProvider seguente è un'implementazione di esempio di una classe AuthenticationStateProvider personalizzata per questo scenario di autenticazione.

Nota

La classe AuthenticationStateProvider personalizzata seguente non dichiara uno spazio dei nomi per fare in modo che l'esempio di codice sia applicabile a qualsiasi app Blazor Hybrid. È tuttavia consigliabile specificare lo spazio dei nomi dell'app quando si implementa l'esempio in un'app di produzione.

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();
}

Nei passaggi successivi viene descritto come effettuare le seguenti operazioni:

  • Aggiungere gli spazi dei nomi obbligatori.
  • Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi.
  • Compilare la raccolta di servizi.
  • Risolvere il servizio AuthenticatedUser per impostare l'entità di sicurezza delle attestazioni dell'utente autenticato. Per informazioni dettagliate, vedere la identity documentazione del provider.
  • Restituire l'host compilato.

Nel metodo MauiProgram.CreateMauiApp di MauiProgram.cs aggiungere gli spazi dei nomi per Microsoft.AspNetCore.Components.Authorization e System.Security.Claims:

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

Rimuovere la riga di codice seguente che restituisce un elemento Microsoft.Maui.Hosting.MauiApp compilato:

- return builder.Build();

Sostituire la riga di codice precedente con il codice seguente. Aggiungere il codice OpenID/MSAL per autenticare l'utente. Per informazioni dettagliate, vedere la identity documentazione del provider.

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;

Nei passaggi successivi viene descritto come effettuare le seguenti operazioni:

  • Aggiungere gli spazi dei nomi obbligatori.
  • Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi.
  • Compilare la raccolta di servizi e aggiungere la raccolta di servizi compilata come risorsa al ResourceDictionary dell'app.
  • Risolvere il servizio AuthenticatedUser per impostare l'entità di sicurezza delle attestazioni dell'utente autenticato. Per informazioni dettagliate, vedere la identity documentazione del provider.
  • Restituire l'host compilato.

Nel costruttore di MainWindow (MainWindow.xaml.cs) aggiungere gli spazi dei nomi per Microsoft.AspNetCore.Components.Authorization e System.Security.Claims:

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

Rimuovere la riga di codice seguente che aggiunge la raccolta di servizi compilata come risorsa al ResourceDictionary dell'app:

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

Sostituire la riga di codice precedente con il codice seguente. Aggiungere il codice OpenID/MSAL per autenticare l'utente. Per informazioni dettagliate, vedere la identity documentazione del provider.

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;

Nei passaggi successivi viene descritto come effettuare le seguenti operazioni:

  • Aggiungere gli spazi dei nomi obbligatori.
  • Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi.
  • Compilare la raccolta di servizi e aggiungere la raccolta di servizi compilata al provider di servizi dell'app.
  • Risolvere il servizio AuthenticatedUser per impostare l'entità di sicurezza delle attestazioni dell'utente autenticato. Per informazioni dettagliate, vedere la identity documentazione del provider.

Nel costruttore di Form1 (Form1.cs) aggiungere gli spazi dei nomi per Microsoft.AspNetCore.Components.Authorization e System.Security.Claims:

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

Rimuovere la riga di codice seguente che imposta la raccolta di servizi compilata sul provider di servizi dell'app:

- blazorWebView1.Services = services.BuildServiceProvider();

Sostituire la riga di codice precedente con il codice seguente. Aggiungere il codice OpenID/MSAL per autenticare l'utente. Per informazioni dettagliate, vedere la identity documentazione del provider.

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;

Creare una classe AuthenticationStateProvider personalizzata con gli aggiornamenti delle modifiche utente

Per aggiornare l'utente durante l'esecuzione dell'app Blazor, chiamare NotifyAuthenticationStateChanged all'interno dell'implementazione di AuthenticationStateProvider usando uno dei due approcci seguenti:

Segnalare un aggiornamento dell'autenticazione dall'esterno di BlazorWebView (opzione 1)

Una classe AuthenticationStateProvider personalizzata può usare un servizio globale per segnalare un aggiornamento dell'autenticazione. È consigliabile che il servizio offra un evento che AuthenticationStateProvider possa sottoscrivere, dove l'evento richiama NotifyAuthenticationStateChanged.

Nota

La classe AuthenticationStateProvider personalizzata seguente non dichiara uno spazio dei nomi per fare in modo che l'esempio di codice sia applicabile a qualsiasi app Blazor Hybrid. È tuttavia consigliabile specificare lo spazio dei nomi dell'app quando si implementa l'esempio in un'app di produzione.

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);
            }
        }
    }
}

Nel metodo MauiProgram.CreateMauiApp di MauiProgram.cs aggiungere uno spazio dei nomi per Microsoft.AspNetCore.Components.Authorization:

using Microsoft.AspNetCore.Components.Authorization;

Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi:

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

Nel costruttore di MainWindow (MainWindow.xaml.cs) aggiungere uno spazio dei nomi per Microsoft.AspNetCore.Components.Authorization:

using Microsoft.AspNetCore.Components.Authorization;

Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi:

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

Nel costruttore di Form1 (Form1.cs) aggiungere uno spazio dei nomi per Microsoft.AspNetCore.Components.Authorization:

using Microsoft.AspNetCore.Components.Authorization;

Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi:

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

Ovunque l'app autentichi un utente, risolvere il servizio ExternalAuthService:

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

Eseguire il codice OpenID/MSAL personalizzato per autenticare l'utente. Per informazioni dettagliate, vedere la identity documentazione del provider. L'utente autenticato (authenticatedUser nell'esempio seguente) è una nuova classe ClaimsPrincipal basata su una nuova classe ClaimsIdentity.

Impostare l'utente corrente sull'utente autenticato:

authService.CurrentUser = authenticatedUser;

Un'alternativa all'approccio precedente consiste nell'impostare l'entità di sicurezza dell'utente su System.Threading.Thread.CurrentPrincipal invece di impostarla tramite un servizio, evitando l'uso del contenitore di inserimento delle dipendenze:

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

Usando l'approccio alternativo, vengono aggiunti alla raccolta di servizi solo i servizi di autorizzazione (AddAuthorizationCore) e CurrentThreadUserAuthenticationStateProvider (.TryAddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()).

Gestire l'autenticazione all'interno di BlazorWebView (opzione 2)

Una classe AuthenticationStateProvider personalizzata può includere metodi aggiuntivi per attivare l'accesso e la disconnessione e aggiornare l'utente.

Nota

La classe AuthenticationStateProvider personalizzata seguente non dichiara uno spazio dei nomi per fare in modo che l'esempio di codice sia applicabile a qualsiasi app Blazor Hybrid. È tuttavia consigliabile specificare lo spazio dei nomi dell'app quando si implementa l'esempio in un'app di produzione.

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)));
    }
}

Nell'esempio precedente:

  • La chiamata a LogInAsyncCore attiva il processo di accesso.
  • La chiamata a NotifyAuthenticationStateChanged notifica che è in corso un aggiornamento, che consente all'app di fornire un'interfaccia utente temporanea durante il processo di accesso o disconnessione.
  • La restituzione di loginTask restituisce l'attività in modo che il componente che ha attivato l'accesso possa attendere e reagire al termine dell'attività.
  • Il LoginWithExternalProviderAsync metodo viene implementato dallo sviluppatore per accedere all'utente con l'SDK identity del provider. Per altre informazioni, vedere la identity documentazione del provider. L'utente autenticato (authenticatedUser) è una nuova classe ClaimsPrincipal basata su una nuova classe ClaimsIdentity.

Nel metodo MauiProgram.CreateMauiApp di MauiProgram.cs aggiungere i servizi di autorizzazione e l'astrazione di Blazor alla raccolta di servizi:

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

Nel costruttore di MainWindow (MainWindow.xaml.cs) aggiungere i servizi di autorizzazione e l'astrazione di Blazor alla raccolta di servizi:

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

Nel costruttore di Form1 (Form1.cs) aggiungere i servizi di autorizzazione e l'astrazione di Blazor alla raccolta di servizi:

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

Il componente LoginComponent seguente mostra come far accedere un utente. In un'app tipica il componente LoginComponent viene visualizzato in un componente padre solo se l'utente non è connesso all'app.

Shared/LoginComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

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

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

Il componente LogoutComponent seguente mostra come disconnettere un utente. In un'app tipica il componente LogoutComponent viene visualizzato in un componente padre solo se l'utente è connesso all'app.

Shared/LogoutComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

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

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

Accesso ad altre informazioni di autenticazione

Blazor non definisce un'astrazione per gestire altre credenziali, ad esempio i token di accesso da usare per le richieste HTTP alle API Web. È consigliabile seguire le identity indicazioni del provider per gestire le credenziali dell'utente con le primitive fornite dall'SDK identity del provider.

È comune che gli identity SDK del provider usino un archivio token per le credenziali utente archiviate nel dispositivo. Se la primitiva dell'archivio dei token dell'SDK viene aggiunta al contenitore di servizi, utilizzare la primitiva dell'SDK all'interno dell'app.

Il framework Blazor non è a conoscenza delle credenziali di autenticazione di un utente e non interagisce con le credenziali in alcun modo, quindi il codice dell'app è libero di seguire qualsiasi approccio ritenuto più pratico. Seguire tuttavia le indicazioni generali sulla sicurezza nella sezione successiva, Altre considerazioni sulla sicurezza dell'autenticazione, quando si implementa il codice di autenticazione in un'app.

Altre considerazioni sulla sicurezza dell'autenticazione

Il processo di autenticazione è esterno a Blazore si consiglia agli sviluppatori di accedere alle identity linee guida del provider per indicazioni aggiuntive sulla sicurezza.

Quando si implementa l'autenticazione:

  • Evitare l'autenticazione nel contesto di Web View. Evitare, ad esempio, di usare una libreria OAuth JavaScript per eseguire il flusso di autenticazione. In un'app a pagina singola i token di autenticazione non sono nascosti in JavaScript e possono essere facilmente individuati da utenti malintenzionati e usati per scopi dannosi. Le app native non corrono questo rischio perché possono ottenere i token solo all'esterno del contesto del browser, quindi gli script di terze parti non autorizzati non possono rubare i token e compromettere l'app.
  • Evitare di implementare manualmente il flusso di lavoro di autenticazione. Nella maggior parte dei casi, le librerie della piattaforma gestiscono in modo sicuro il flusso di lavoro di autenticazione, usando il browser del sistema invece di una Web View personalizzata che può essere soggetta a hijack.
  • Evitare di usare il controllo Web View della piattaforma per eseguire l'autenticazione. Fare invece affidamento sul browser del sistema, quando possibile.
  • Evitare di passare i token al contesto del documento (JavaScript). In alcuni casi, è necessaria una libreria JavaScript all'interno del documento per eseguire una chiamata autorizzata a un servizio esterno. Invece di rendere il token disponibile per JavaScript tramite JS interoperabilità:
    • Fornire un token temporaneo generato alla libreria e all'interno del controllo Web View.
    • Intercettare la richiesta di rete in uscita nel codice.
    • Sostituire il token temporaneo con il token reale e verificare che la destinazione della richiesta sia valida.

Risorse aggiuntive

L'autenticazione nelle app Blazor Hybrid viene gestita dalle librerie della piattaforma nativa perché offrono garanzie di sicurezza avanzata che la sandbox del browser non può offrire. L'autenticazione delle app native usa un meccanismo specifico del sistema operativo o un protocollo federato, ad esempio OpenID Connect (OIDC). Seguire le indicazioni per il identity provider selezionato per l'app e quindi integrarsi identity ulteriormente con Blazor l'uso delle indicazioni riportate in questo articolo.

L'integrazione dell'autenticazione deve raggiungere gli obiettivi seguenti per i componenti e i servizi di Razor:

  • Usare le astrazioni nel pacchetto Microsoft.AspNetCore.Components.Authorization, ad esempio AuthorizeView.
  • Reagire alle modifiche nel contesto di autenticazione.
  • Credenziali di accesso di cui viene effettuato il identity provisioning dall'app dal provider, ad esempio i token di accesso per eseguire chiamate API autorizzate.

Dopo che l'autenticazione è stata aggiunta a un'app .NET MAUI, WPF o Windows Form e gli utenti possono accedere e disconnettersi correttamente, integrare l'autenticazione con Blazor per rendere l'utente autenticato disponibile per i componenti e i servizi di Razor. Procedi come segue:

Le app .NET MAUI usano l'autenticatore Web di Xamarin.Essentials: la classe WebAuthenticator consente all'app di avviare i flussi di autenticazione basata sul browser che sono in ascolto di un callback a un URL specifico registrato con l'app.

Per altre indicazioni, vedere le risorse seguenti:

Windows Form le app usano Piattaforma Microsoft identity da integrare con Microsoft Entra (ME-ID) e AAD B2C. Per altre informazioni, vedere Panoramica di Microsoft Authentication Library (MSAL).

Creare una classe AuthenticationStateProvider personalizzata senza aggiornamenti delle modifiche utente

Se l'app autentica l'utente immediatamente dopo l'avvio dell'app e l'utente autenticato rimane lo stesso per tutta la durata dell'app, le notifiche di modifica utente non sono necessarie e l'app fornisce solo informazioni sull'utente autenticato. In questo scenario l'utente accede all'app quando l'app viene aperta e l'app visualizza nuovamente la schermata di accesso dopo la disconnessione dell'utente. L'elemento ExternalAuthStateProvider seguente è un'implementazione di esempio di una classe AuthenticationStateProvider personalizzata per questo scenario di autenticazione.

Nota

La classe AuthenticationStateProvider personalizzata seguente non dichiara uno spazio dei nomi per fare in modo che l'esempio di codice sia applicabile a qualsiasi app Blazor Hybrid. È tuttavia consigliabile specificare lo spazio dei nomi dell'app quando si implementa l'esempio in un'app di produzione.

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();
}

Nei passaggi successivi viene descritto come effettuare le seguenti operazioni:

  • Aggiungere gli spazi dei nomi obbligatori.
  • Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi.
  • Compilare la raccolta di servizi.
  • Risolvere il servizio AuthenticatedUser per impostare l'entità di sicurezza delle attestazioni dell'utente autenticato. Per informazioni dettagliate, vedere la identity documentazione del provider.
  • Restituire l'host compilato.

Nel metodo MauiProgram.CreateMauiApp di MauiProgram.cs aggiungere gli spazi dei nomi per Microsoft.AspNetCore.Components.Authorization e System.Security.Claims:

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

Rimuovere la riga di codice seguente che restituisce un elemento Microsoft.Maui.Hosting.MauiApp compilato:

- return builder.Build();

Sostituire la riga di codice precedente con il codice seguente. Aggiungere il codice OpenID/MSAL per autenticare l'utente. Per informazioni dettagliate, vedere la identity documentazione del provider.

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;

Nei passaggi successivi viene descritto come effettuare le seguenti operazioni:

  • Aggiungere gli spazi dei nomi obbligatori.
  • Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi.
  • Compilare la raccolta di servizi e aggiungere la raccolta di servizi compilata come risorsa al ResourceDictionary dell'app.
  • Risolvere il servizio AuthenticatedUser per impostare l'entità di sicurezza delle attestazioni dell'utente autenticato. Per informazioni dettagliate, vedere la identity documentazione del provider.
  • Restituire l'host compilato.

Nel costruttore di MainWindow (MainWindow.xaml.cs) aggiungere gli spazi dei nomi per Microsoft.AspNetCore.Components.Authorization e System.Security.Claims:

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

Rimuovere la riga di codice seguente che aggiunge la raccolta di servizi compilata come risorsa al ResourceDictionary dell'app:

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

Sostituire la riga di codice precedente con il codice seguente. Aggiungere il codice OpenID/MSAL per autenticare l'utente. Per informazioni dettagliate, vedere la identity documentazione del provider.

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;

Nei passaggi successivi viene descritto come effettuare le seguenti operazioni:

  • Aggiungere gli spazi dei nomi obbligatori.
  • Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi.
  • Compilare la raccolta di servizi e aggiungere la raccolta di servizi compilata al provider di servizi dell'app.
  • Risolvere il servizio AuthenticatedUser per impostare l'entità di sicurezza delle attestazioni dell'utente autenticato. Per informazioni dettagliate, vedere la identity documentazione del provider.

Nel costruttore di Form1 (Form1.cs) aggiungere gli spazi dei nomi per Microsoft.AspNetCore.Components.Authorization e System.Security.Claims:

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

Rimuovere la riga di codice seguente che imposta la raccolta di servizi compilata sul provider di servizi dell'app:

- blazorWebView1.Services = services.BuildServiceProvider();

Sostituire la riga di codice precedente con il codice seguente. Aggiungere il codice OpenID/MSAL per autenticare l'utente. Per informazioni dettagliate, vedere la identity documentazione del provider.

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;

Creare una classe AuthenticationStateProvider personalizzata con gli aggiornamenti delle modifiche utente

Per aggiornare l'utente durante l'esecuzione dell'app Blazor, chiamare NotifyAuthenticationStateChanged all'interno dell'implementazione di AuthenticationStateProvider usando uno dei due approcci seguenti:

Segnalare un aggiornamento dell'autenticazione dall'esterno di BlazorWebView (opzione 1)

Una classe AuthenticationStateProvider personalizzata può usare un servizio globale per segnalare un aggiornamento dell'autenticazione. È consigliabile che il servizio offra un evento che AuthenticationStateProvider possa sottoscrivere, dove l'evento richiama NotifyAuthenticationStateChanged.

Nota

La classe AuthenticationStateProvider personalizzata seguente non dichiara uno spazio dei nomi per fare in modo che l'esempio di codice sia applicabile a qualsiasi app Blazor Hybrid. È tuttavia consigliabile specificare lo spazio dei nomi dell'app quando si implementa l'esempio in un'app di produzione.

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);
            }
        }
    }
}

Nel metodo MauiProgram.CreateMauiApp di MauiProgram.cs aggiungere uno spazio dei nomi per Microsoft.AspNetCore.Components.Authorization:

using Microsoft.AspNetCore.Components.Authorization;

Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi:

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

Nel costruttore di MainWindow (MainWindow.xaml.cs) aggiungere uno spazio dei nomi per Microsoft.AspNetCore.Components.Authorization:

using Microsoft.AspNetCore.Components.Authorization;

Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi:

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

Nel costruttore di Form1 (Form1.cs) aggiungere uno spazio dei nomi per Microsoft.AspNetCore.Components.Authorization:

using Microsoft.AspNetCore.Components.Authorization;

Aggiungere i servizi di autorizzazione e le astrazioni di Blazor alla raccolta di servizi:

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

Ovunque l'app autentichi un utente, risolvere il servizio ExternalAuthService:

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

Eseguire il codice OpenID/MSAL personalizzato per autenticare l'utente. Per informazioni dettagliate, vedere la identity documentazione del provider. L'utente autenticato (authenticatedUser nell'esempio seguente) è una nuova classe ClaimsPrincipal basata su una nuova classe ClaimsIdentity.

Impostare l'utente corrente sull'utente autenticato:

authService.CurrentUser = authenticatedUser;

Un'alternativa all'approccio precedente consiste nell'impostare l'entità di sicurezza dell'utente su System.Threading.Thread.CurrentPrincipal invece di impostarla tramite un servizio, evitando l'uso del contenitore di inserimento delle dipendenze:

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

Usando l'approccio alternativo, vengono aggiunti alla raccolta di servizi solo i servizi di autorizzazione (AddAuthorizationCore) e CurrentThreadUserAuthenticationStateProvider (.AddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()).

Gestire l'autenticazione all'interno di BlazorWebView (opzione 2)

Una classe AuthenticationStateProvider personalizzata può includere metodi aggiuntivi per attivare l'accesso e la disconnessione e aggiornare l'utente.

Nota

La classe AuthenticationStateProvider personalizzata seguente non dichiara uno spazio dei nomi per fare in modo che l'esempio di codice sia applicabile a qualsiasi app Blazor Hybrid. È tuttavia consigliabile specificare lo spazio dei nomi dell'app quando si implementa l'esempio in un'app di produzione.

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)));
    }
}

Nell'esempio precedente:

  • La chiamata a LogInAsyncCore attiva il processo di accesso.
  • La chiamata a NotifyAuthenticationStateChanged notifica che è in corso un aggiornamento, che consente all'app di fornire un'interfaccia utente temporanea durante il processo di accesso o disconnessione.
  • La restituzione di loginTask restituisce l'attività in modo che il componente che ha attivato l'accesso possa attendere e reagire al termine dell'attività.
  • Il LoginWithExternalProviderAsync metodo viene implementato dallo sviluppatore per accedere all'utente con l'SDK identity del provider. Per altre informazioni, vedere la identity documentazione del provider. L'utente autenticato (authenticatedUser) è una nuova classe ClaimsPrincipal basata su una nuova classe ClaimsIdentity.

Nel metodo MauiProgram.CreateMauiApp di MauiProgram.cs aggiungere i servizi di autorizzazione e l'astrazione di Blazor alla raccolta di servizi:

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

Nel costruttore di MainWindow (MainWindow.xaml.cs) aggiungere i servizi di autorizzazione e l'astrazione di Blazor alla raccolta di servizi:

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

Nel costruttore di Form1 (Form1.cs) aggiungere i servizi di autorizzazione e l'astrazione di Blazor alla raccolta di servizi:

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

Il componente LoginComponent seguente mostra come far accedere un utente. In un'app tipica il componente LoginComponent viene visualizzato in un componente padre solo se l'utente non è connesso all'app.

Shared/LoginComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

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

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

Il componente LogoutComponent seguente mostra come disconnettere un utente. In un'app tipica il componente LogoutComponent viene visualizzato in un componente padre solo se l'utente è connesso all'app.

Shared/LogoutComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

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

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

Accesso ad altre informazioni di autenticazione

Blazor non definisce un'astrazione per gestire altre credenziali, ad esempio i token di accesso da usare per le richieste HTTP alle API Web. È consigliabile seguire le identity indicazioni del provider per gestire le credenziali dell'utente con le primitive fornite dall'SDK identity del provider.

È comune che gli identity SDK del provider usino un archivio token per le credenziali utente archiviate nel dispositivo. Se la primitiva dell'archivio dei token dell'SDK viene aggiunta al contenitore di servizi, utilizzare la primitiva dell'SDK all'interno dell'app.

Il framework Blazor non è a conoscenza delle credenziali di autenticazione di un utente e non interagisce con le credenziali in alcun modo, quindi il codice dell'app è libero di seguire qualsiasi approccio ritenuto più pratico. Seguire tuttavia le indicazioni generali sulla sicurezza nella sezione successiva, Altre considerazioni sulla sicurezza dell'autenticazione, quando si implementa il codice di autenticazione in un'app.

Altre considerazioni sulla sicurezza dell'autenticazione

Il processo di autenticazione è esterno a Blazore si consiglia agli sviluppatori di accedere alle identity linee guida del provider per indicazioni aggiuntive sulla sicurezza.

Quando si implementa l'autenticazione:

  • Evitare l'autenticazione nel contesto di Web View. Evitare, ad esempio, di usare una libreria OAuth JavaScript per eseguire il flusso di autenticazione. In un'app a pagina singola i token di autenticazione non sono nascosti in JavaScript e possono essere facilmente individuati da utenti malintenzionati e usati per scopi dannosi. Le app native non corrono questo rischio perché possono ottenere i token solo all'esterno del contesto del browser, quindi gli script di terze parti non autorizzati non possono rubare i token e compromettere l'app.
  • Evitare di implementare manualmente il flusso di lavoro di autenticazione. Nella maggior parte dei casi, le librerie della piattaforma gestiscono in modo sicuro il flusso di lavoro di autenticazione, usando il browser del sistema invece di una Web View personalizzata che può essere soggetta a hijack.
  • Evitare di usare il controllo Web View della piattaforma per eseguire l'autenticazione. Fare invece affidamento sul browser del sistema, quando possibile.
  • Evitare di passare i token al contesto del documento (JavaScript). In alcuni casi, è necessaria una libreria JavaScript all'interno del documento per eseguire una chiamata autorizzata a un servizio esterno. Invece di rendere il token disponibile per JavaScript tramite JS interoperabilità:
    • Fornire un token temporaneo generato alla libreria e all'interno del controllo Web View.
    • Intercettare la richiesta di rete in uscita nel codice.
    • Sostituire il token temporaneo con il token reale e verificare che la destinazione della richiesta sia valida.

Risorse aggiuntive