ASP.NET scenari di sicurezza aggiuntivi core Blazor WebAssembly

Nota

Questa non è la versione più recente di questo articolo. 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 8 di questo articolo.

Questo articolo descrive altri scenari di sicurezza per Blazor WebAssembly le app.

Collegare i token alle richieste in uscita

AuthorizationMessageHandlerDelegatingHandler viene usato per elaborare i token di accesso. I token vengono acquisiti usando il IAccessTokenProvider servizio, registrato dal framework. Se non è possibile acquisire un token, viene generata un'eccezione AccessTokenNotAvailableException . AccessTokenNotAvailableException dispone di un Redirect metodo che passa all'uso AccessTokenResult.InteractiveRequestUrl dell'oggetto specificato AccessTokenResult.InteractionOptions per consentire l'aggiornamento del token di accesso.

Per praticità, il framework fornisce il BaseAddressAuthorizationMessageHandler preconfigurato con l'indirizzo di base dell'app come URL autorizzato. I token di accesso vengono aggiunti solo quando l'URI della richiesta si trova nell'URI di base dell'app. Quando gli URI delle richieste in uscita non sono inclusi nell'URI di base dell'app, usare una classe personalizzata AuthorizationMessageHandler (scelta consigliata) o configurare .AuthorizationMessageHandler

Nota

Oltre alla configurazione dell'app client per l'accesso all'API server, l'API server deve anche consentire le richieste tra le origini (CORS) quando il client e il server non risiedono nello stesso indirizzo di base. Per altre informazioni sulla configurazione CORS sul lato server, vedere la sezione Condivisione risorse tra le origini (CORS) più avanti in questo articolo.

Nell'esempio seguente :

Nell'esempio seguente è HttpClientFactoryServiceCollectionExtensions.AddHttpClient un'estensione in Microsoft.Extensions.Http. Aggiungere il pacchetto a un'app che non vi fa già riferimento.

Nota

Per indicazioni sull'aggiunta di pacchetti alle app .NET, vedere gli articoli sotto Installare e gestire pacchetti in Flusso di lavoro dell'utilizzo di pacchetti (documentazione di NuGet). Confermare le versioni corrette del pacchetto all'indirizzo NuGet.org.

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient("WebAPI", 
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("WebAPI"));

Per una soluzione ospitataBlazor basata sul modello di Blazor WebAssembly progetto, gli URI della richiesta si trovano all'interno dell'URI di base dell'app per impostazione predefinita. Pertanto, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) viene assegnato a HttpClient.BaseAddress in un'app generata dal modello di progetto.

L'oggetto configurato HttpClient viene usato per effettuare richieste autorizzate usando il try-catch modello :

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http

...

protected override async Task OnInitializedAsync()
{
    try
    {
        var examples = 
            await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

        ...
    }
    catch (AccessTokenNotAvailableException exception)
    {
        exception.Redirect();
    }
}

Scenari di richiesta di autenticazione personalizzata

Gli scenari seguenti illustrano come personalizzare le richieste di autenticazione e come ottenere il percorso di accesso dalle opzioni di autenticazione.

Personalizzare il processo di accesso

Gestire parametri aggiuntivi per una richiesta di accesso con i metodi seguenti una o più volte in una nuova istanza di InteractiveRequestOptions:

Nell'esempio di componente seguente LoginDisplay vengono aggiunti parametri aggiuntivi alla richiesta di accesso:

  • prompt è impostato su login: forza l'utente a immettere le proprie credenziali in tale richiesta, negando l'accesso Single Sign-On.
  • loginHint è impostato su peter@contoso.com: inserisce il campo nome utente/indirizzo di posta elettronica della pagina di accesso per l'utente su peter@contoso.com. Le app usano spesso questo parametro durante l'autenticazione di nuovo, avendo già estratto il nome utente da un accesso precedente usando l'attestazione preferred_username .

Shared/LoginDisplay.razor:

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity?.Name!
        <button @onclick="BeginLogOut">Log out</button>
    </Authorized>
    <NotAuthorized>
        <button @onclick="BeginLogIn">Log in</button>
    </NotAuthorized>
</AuthorizeView>

@code{
    public void BeginLogOut()
    {
        Navigation.NavigateToLogout("authentication/logout");
    }

    public void BeginLogIn()
    {
        InteractiveRequestOptions requestOptions =
            new()
            {
                Interaction = InteractionType.SignIn,
                ReturnUrl = Navigation.Uri,
            };

        requestOptions.TryAddAdditionalParameter("prompt", "login");
        requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");

        Navigation.NavigateToLogin("authentication/login", requestOptions);
    }
}

Per ulteriori informazioni, vedi le seguenti risorse:

Personalizzare le opzioni prima di ottenere un token in modo interattivo

Se si verifica un oggetto AccessTokenNotAvailableException , gestire parametri aggiuntivi per una nuova richiesta di token di accesso del provider di identità con i metodi seguenti una o più volte in una nuova istanza di InteractiveRequestOptions:

Nell'esempio seguente che ottiene dati JSON tramite l'API Web vengono aggiunti parametri aggiuntivi alla richiesta di reindirizzamento se non è disponibile un token di accesso (AccessTokenNotAvailableException viene generata):

  • prompt è impostato su login: forza l'utente a immettere le proprie credenziali in tale richiesta, negando l'accesso Single Sign-On.
  • loginHint è impostato su peter@contoso.com: inserisce il campo nome utente/indirizzo di posta elettronica della pagina di accesso per l'utente su peter@contoso.com. Le app usano spesso questo parametro durante l'autenticazione di nuovo, avendo già estratto il nome utente da un accesso precedente usando l'attestazione preferred_username .
try
{
    var examples = await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

    ...
}
catch (AccessTokenNotAvailableException ex)
{
    ex.Redirect(requestOptions => {
        requestOptions.TryAddAdditionalParameter("prompt", "login");
        requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");
    });
}

Nell'esempio precedente si presuppone che:

Per ulteriori informazioni, vedi le seguenti risorse:

Personalizzare le opzioni quando si usa un IAccessTokenProvider

Se l'acquisizione di un token non riesce quando si usa un IAccessTokenProvider, gestire parametri aggiuntivi per una nuova richiesta di token di accesso del provider di identità con uno o più metodi seguenti in una nuova istanza di InteractiveRequestOptions:

Nell'esempio seguente che tenta di ottenere un token di accesso per l'utente vengono aggiunti parametri aggiuntivi alla richiesta di accesso se il tentativo di ottenere un token non riesce quando TryGetToken viene chiamato:

  • prompt è impostato su login: forza l'utente a immettere le proprie credenziali in tale richiesta, negando l'accesso Single Sign-On.
  • loginHint è impostato su peter@contoso.com: inserisce il campo nome utente/indirizzo di posta elettronica della pagina di accesso per l'utente su peter@contoso.com. Le app usano spesso questo parametro durante l'autenticazione di nuovo, avendo già estratto il nome utente da un accesso precedente usando l'attestazione preferred_username .
var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { ... }
    });

if (!tokenResult.TryGetToken(out var token))
{
    tokenResult.InteractionOptions.TryAddAdditionalParameter("prompt", "login");
    tokenResult.InteractionOptions.TryAddAdditionalParameter("loginHint", 
        "peter@contoso.com");

    Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl, 
        accessTokenResult.InteractionOptions);
}

L'esempio precedente presuppone:

Per ulteriori informazioni, vedi le seguenti risorse:

Disconnettersi con un URL restituito personalizzato

L'esempio seguente disconnette l'utente e restituisce l'utente all'endpoint /goodbye :

Navigation.NavigateToLogout("authentication/logout", "goodbye");

Ottenere il percorso di accesso dalle opzioni di autenticazione

Ottenere il percorso di accesso configurato da RemoteAuthenticationOptions:

var loginPath = 
    RemoteAuthOptions.Get(Options.DefaultName).AuthenticationPaths.LogInPath;

L'esempio precedente presuppone:

Classe personalizzata AuthorizationMessageHandler

Questa guida in questa sezione è consigliata per le app client che effettuano richieste in uscita agli URI che non si trovano nell'URI di base dell'app.

Nell'esempio seguente una classe personalizzata si estende AuthorizationMessageHandler per l'uso DelegatingHandler come per un oggetto HttpClient. ConfigureHandler configura questo gestore per autorizzare le richieste HTTP in uscita usando un token di accesso. Il token di accesso è associato solo se almeno uno degli URL autorizzati è una base dell'URI della richiesta (HttpRequestMessage.RequestUri).

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, 
        NavigationManager navigation)
        : base(provider, navigation)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "https://api.contoso.com/v1.0" },
            scopes: new[] { "example.read", "example.write" });
    }
}

Nel codice precedente, gli ambiti e example.write sono esempi generici example.read non destinati a riflettere ambiti validi per qualsiasi provider specifico.

Program Nel file CustomAuthorizationMessageHandler viene registrato come servizio temporaneo ed è configurato come per DelegatingHandler le istanze in uscita HttpResponseMessage effettuate da un denominato HttpClient.

Nell'esempio seguente è HttpClientFactoryServiceCollectionExtensions.AddHttpClient un'estensione in Microsoft.Extensions.Http. Aggiungere il pacchetto a un'app che non vi fa già riferimento.

Nota

Per indicazioni sull'aggiunta di pacchetti alle app .NET, vedere gli articoli sotto Installare e gestire pacchetti in Flusso di lavoro dell'utilizzo di pacchetti (documentazione di NuGet). Confermare le versioni corrette del pacchetto all'indirizzo NuGet.org.

builder.Services.AddTransient<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient("WebAPI",
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

Nota

Nell'esempio precedente l'oggetto CustomAuthorizationMessageHandlerDelegatingHandler viene registrato come servizio temporaneo per AddHttpMessageHandler. La registrazione temporanea è consigliata per IHttpClientFactory, che gestisce i propri ambiti di inserimento delle dipendenze. Per ulteriori informazioni, vedi le seguenti risorse:

Per una soluzione ospitata Blazor basata sul modello diBlazor WebAssemblyprogetto, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) viene assegnata all'oggetto HttpClient.BaseAddress per impostazione predefinita.

L'oggetto configurato HttpClient viene usato per effettuare richieste autorizzate usando il try-catch modello . Dove viene creato il client con CreateClient (Microsoft.Extensions.Http pacchetto), HttpClient vengono fornite le istanze che includono token di accesso durante l'esecuzione di richieste all'API server. Se l'URI della richiesta è un URI relativo, come nell'esempio seguente (ExampleAPIMethod), viene combinato con BaseAddress quando l'app client effettua la richiesta:

@inject IHttpClientFactory ClientFactory

...

@code {
    protected override async Task OnInitializedAsync()
    {
        try
        {
            var client = ClientFactory.CreateClient("WebAPI");

            var examples = 
                await client.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

            ...
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

ConfigurareAuthorizationMessageHandler

AuthorizationMessageHandler può essere configurato con URL, ambiti e URL restituiti autorizzati usando il ConfigureHandler metodo . ConfigureHandler configura il gestore per autorizzare le richieste HTTP in uscita usando un token di accesso. Il token di accesso è associato solo se almeno uno degli URL autorizzati è una base dell'URI della richiesta (HttpRequestMessage.RequestUri). Se l'URI della richiesta è un URI relativo, viene combinato con .BaseAddress

Nell'esempio seguente viene AuthorizationMessageHandler configurato un oggetto HttpClient nel Program file :

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddScoped(sp => new HttpClient(
    sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new[] { "https://api.contoso.com/v1.0" },
        scopes: new[] { "example.read", "example.write" }))
    {
        BaseAddress = new Uri("https://api.contoso.com/v1.0")
    });

Nel codice precedente, gli ambiti e example.write sono esempi generici example.read non destinati a riflettere ambiti validi per qualsiasi provider specifico.

Per una soluzione ospitata Blazor basata sul modello di Blazor WebAssembly progetto, IWebAssemblyHostEnvironment.BaseAddress viene assegnata alla seguente per impostazione predefinita:

Digitato HttpClient

È possibile definire un client tipizzato che gestisce tutti i problemi di acquisizione http e token all'interno di una singola classe.

WeatherForecastClient.cs:

using System.Net.Http.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {ASSEMBLY NAME}.Data;

public class WeatherForecastClient
{
    private readonly HttpClient http;
    private WeatherForecast[]? forecasts;

    public WeatherForecastClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        try
        {
            forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }

        return forecasts ?? Array.Empty<WeatherForecast>();
    }
}
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {ASSEMBLY NAME}.Data;

public class WeatherForecastClient
{
    private readonly HttpClient http;
    private WeatherForecast[] forecasts;

    public WeatherForecastClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        try
        {
            forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }

        return forecasts ?? Array.Empty<WeatherForecast>();
    }
}

Nell'esempio precedente, il WeatherForecast tipo è una classe statica che contiene i dati delle previsioni meteo. Il segnaposto {ASSEMBLY NAME} è il nome dell'assembly dell'app , ad esempio using static BlazorSample.Data;.

Nell'esempio seguente è HttpClientFactoryServiceCollectionExtensions.AddHttpClient un'estensione in Microsoft.Extensions.Http. Aggiungere il pacchetto a un'app che non vi fa già riferimento.

Nota

Per indicazioni sull'aggiunta di pacchetti alle app .NET, vedere gli articoli sotto Installare e gestire pacchetti in Flusso di lavoro dell'utilizzo di pacchetti (documentazione di NuGet). Confermare le versioni corrette del pacchetto all'indirizzo NuGet.org.

Nel file Program:

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

Per una soluzione ospitata Blazor basata sul modello diBlazor WebAssemblyprogetto, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) viene assegnata all'oggetto HttpClient.BaseAddress per impostazione predefinita.

In un componente che recupera i dati meteo:

@inject WeatherForecastClient Client

...

protected override async Task OnInitializedAsync()
{
    forecasts = await Client.GetForecastAsync();
}

Configurare il HttpClient gestore

Il gestore può essere ulteriormente configurato con ConfigureHandler per le richieste HTTP in uscita.

Nell'esempio seguente è HttpClientFactoryServiceCollectionExtensions.AddHttpClient un'estensione in Microsoft.Extensions.Http. Aggiungere il pacchetto a un'app che non vi fa già riferimento.

Nota

Per indicazioni sull'aggiunta di pacchetti alle app .NET, vedere gli articoli sotto Installare e gestire pacchetti in Flusso di lavoro dell'utilizzo di pacchetti (documentazione di NuGet). Confermare le versioni corrette del pacchetto all'indirizzo NuGet.org.

Nel file Program:

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new [] { "https://api.contoso.com/v1.0" },
        scopes: new[] { "example.read", "example.write" }));

Nel codice precedente, gli ambiti e example.write sono esempi generici example.read non destinati a riflettere ambiti validi per qualsiasi provider specifico.

Per una soluzione ospitata Blazor basata sul modello di Blazor WebAssembly progetto, IWebAssemblyHostEnvironment.BaseAddress viene assegnata alla seguente per impostazione predefinita:

Richieste API Web non autenticate o non autorizzate in un'app con un client predefinito sicuro

Un'app che usa in genere un valore predefinito HttpClient sicuro può anche effettuare richieste API Web non autenticate o non autorizzate configurando un oggetto denominato HttpClient.

Nell'esempio seguente è HttpClientFactoryServiceCollectionExtensions.AddHttpClient un'estensione in Microsoft.Extensions.Http. Aggiungere il pacchetto a un'app che non vi fa già riferimento.

Nota

Per indicazioni sull'aggiunta di pacchetti alle app .NET, vedere gli articoli sotto Installare e gestire pacchetti in Flusso di lavoro dell'utilizzo di pacchetti (documentazione di NuGet). Confermare le versioni corrette del pacchetto all'indirizzo NuGet.org.

Nel file Program:

builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient", 
    client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"));

Per una soluzione ospitata Blazor basata sul modello diBlazor WebAssemblyprogetto, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) viene assegnata all'oggetto HttpClient.BaseAddress per impostazione predefinita.

La registrazione precedente è aggiunta alla registrazione predefinita HttpClient sicura esistente.

Un componente crea da HttpClientIHttpClientFactory (Microsoft.Extensions.Http pacchetto) per effettuare richieste non autenticate o non autorizzate:

@inject IHttpClientFactory ClientFactory

...

@code {
    protected override async Task OnInitializedAsync()
    {
        var client = ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");

        var examples = await client.GetFromJsonAsync<ExampleType[]>(
            "ExampleNoAuthentication");

        ...
    }
}

Nota

Il controller nell'API server, ExampleNoAuthenticationController per l'esempio precedente, non è contrassegnato con l'attributo[Authorize].

La decisione se usare un client sicuro o un client non sicuro perché l'istanza predefinita HttpClient spetta allo sviluppatore. Un modo per prendere questa decisione consiste nel considerare il numero di endpoint autenticati rispetto a endpoint non autenticati contattati dall'app. Se la maggior parte delle richieste dell'app è quella di proteggere gli endpoint API, usare l'istanza autenticata HttpClient come predefinita. In caso contrario, registrare l'istanza non autenticata HttpClient come predefinita.

Un approccio alternativo all'uso IHttpClientFactory di consiste nel creare un client tipizzato per l'accesso non autenticato agli endpoint anonimi.

Richiedere token di accesso aggiuntivi

I token di accesso possono essere ottenuti manualmente chiamando IAccessTokenProvider.RequestAccessToken. Nell'esempio seguente un ambito aggiuntivo è richiesto da un'app per l'impostazione predefinita HttpClient. L'esempio di Microsoft Authentication Library (MSAL) configura l'ambito con MsalProviderOptions:

Nel file Program:

builder.Services.AddMsalAuthentication(options =>
{
    ...

    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 1}");
    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 2}");
}

I {CUSTOM SCOPE 1} segnaposto e {CUSTOM SCOPE 2} nell'esempio precedente sono ambiti personalizzati.

Nota

AdditionalScopesToConsent non è in grado di effettuare il provisioning delle autorizzazioni utente delegate per Microsoft Graph tramite l'interfaccia utente di consenso dell'ID di Microsoft Entra quando un utente usa per la prima volta un'app registrata in Microsoft Azure. Per altre informazioni, vedere Usare l'API Graph con ASP.NET Core Blazor WebAssembly.

Il IAccessTokenProvider.RequestAccessToken metodo fornisce un overload che consente a un'app di effettuare il provisioning di un token di accesso con un determinato set di ambiti.

In un Razor componente:

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
    });

if (tokenResult.TryGetToken(out var token))
{
    ...
}

I {CUSTOM SCOPE 1} segnaposto e {CUSTOM SCOPE 2} nell'esempio precedente sono ambiti personalizzati.

AccessTokenResult.TryGetToken Restituisce:

  • true con per l'uso token .
  • false se il token non viene recuperato.

Cross-Origin Resource Sharing (CORS)

Quando si inviano credenziali (intestazioni/s di autorizzazione cookie) nelle richieste CORS, l'intestazione Authorization deve essere consentita dai criteri CORS.

I criteri seguenti includono la configurazione per:

  • Richiedere origini (http://localhost:5000, https://localhost:5001).
  • Qualsiasi metodo (verbo).
  • Content-Type intestazioni e Authorization . Per consentire un'intestazione personalizzata ,ad esempio , x-custom-headerelencare l'intestazione quando si chiama WithHeaders.
  • Credenziali impostate dal codice JavaScript sul lato client (credentials proprietà impostata su include).
app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
        .AllowAnyMethod()
        .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, 
            "x-custom-header")
        .AllowCredentials());

Una soluzione ospitata Blazor basata sul modello di Blazor WebAssembly progetto usa lo stesso indirizzo di base per le app client e server. L'app HttpClient.BaseAddress client è impostata su un URI di builder.HostEnvironment.BaseAddress per impostazione predefinita. La configurazione CORS non è necessaria nella configurazione predefinita di una soluzione ospitataBlazor. Le app client aggiuntive che non sono ospitate dal progetto server e che non condividono l'indirizzo di base dell'app server richiedono la configurazione CORS nel progetto server.

Per altre informazioni, vedere Abilitare le richieste tra le origini (CORS) in ASP.NET Core e il componente http request tester dell'app di esempio (Components/HTTPRequestTester.razor).

Gestire gli errori di richiesta del token

Quando un'applicazione a pagina singola autentica un utente usando OpenID Connessione (OIDC), lo stato di autenticazione viene mantenuto localmente all'interno dell'applicazione a pagina singola e nel Identity provider (IP) sotto forma di sessione cookie impostata come risultato dell'utente che fornisce le proprie credenziali.

I token generati dall'IP per l'utente in genere sono validi per brevi periodi di tempo, in genere circa un'ora, quindi l'app client deve recuperare regolarmente nuovi token. In caso contrario, l'utente verrà disconnesso dopo la scadenza dei token concessi. Nella maggior parte dei casi, i client OIDC sono in grado di effettuare il provisioning di nuovi token senza richiedere all'utente di eseguire di nuovo l'autenticazione grazie allo stato di autenticazione o alla "sessione" mantenuta all'interno dell'IP.

Esistono alcuni casi in cui il client non riesce a ottenere un token senza interazione dell'utente, ad esempio quando per qualche motivo l'utente disconnette esplicitamente dall'IP. Questo scenario si verifica se un utente visita https://login.microsoftonline.com e si disconnette. In questi scenari, l'app non sa immediatamente che l'utente ha disconnesso. Qualsiasi token che il client contiene potrebbe non essere più valido. Inoltre, il client non è in grado di effettuare il provisioning di un nuovo token senza interazione dell'utente dopo la scadenza del token corrente.

Questi scenari non sono specifici dell'autenticazione basata su token. Fanno parte della natura dei contratti di servizio. Un'applicazione a pagina singola che usa cookies non riesce anche a chiamare un'API server se l'autenticazione cookie viene rimossa.

Quando un'app esegue chiamate API alle risorse protette, è necessario tenere presente quanto segue:

  • Per effettuare il provisioning di un nuovo token di accesso per chiamare l'API, potrebbe essere necessario eseguire di nuovo l'autenticazione.
  • Anche se il client ha un token che sembra essere valido, la chiamata al server potrebbe non riuscire perché il token è stato revocato dall'utente.

Quando l'app richiede un token, esistono due possibili risultati:

  • La richiesta ha esito positivo e l'app ha un token valido.
  • La richiesta ha esito negativo e l'app deve autenticare nuovamente l'utente per ottenere un nuovo token.

Quando una richiesta di token ha esito negativo, è necessario decidere se si vuole salvare uno stato corrente prima di eseguire un reindirizzamento. Esistono diversi approcci per archiviare lo stato con livelli crescenti di complessità:

  • Archiviare lo stato della pagina corrente nell'archiviazione delle sessioni. Durante il OnInitializedAsync metodo del ciclo di vita (OnInitializedAsync), verificare se è possibile ripristinare lo stato prima di continuare.
  • Aggiungere un parametro della stringa di query e usarlo come modo per segnalare all'app che deve riattivare lo stato salvato in precedenza.
  • Aggiungere un parametro della stringa di query con un identificatore univoco per archiviare i dati nell'archivio sessioni senza rischiare conflitti con altri elementi.

Salvare lo stato dell'app prima di un'operazione di autenticazione con l'archiviazione di sessione

L'esempio seguente illustra come:

  • Mantenere lo stato prima del reindirizzamento alla pagina di accesso.
  • Ripristinare lo stato precedente dopo l'autenticazione usando un parametro della stringa di query.
...
@using System.Text.Json
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject IJSRuntime JS
@inject NavigationManager Navigation

<EditForm Model="User" OnSubmit="OnSaveAsync">
    <label>
        First Name: 
        <InputText @bind-Value="User!.Name" />
    </label>
    <label>
        Last Name: 
        <InputText @bind-Value="User!.LastName" />
    </label>
    <button type="submit">Save User</button>
</EditForm>

@code {
    public Profile User { get; set; } = new Profile();

    protected override async Task OnInitializedAsync()
    {
        var currentQuery = new Uri(Navigation.Uri).Query;

        if (currentQuery.Contains("state=resumeSavingProfile"))
        {
            var user = await JS.InvokeAsync<string>("sessionStorage.getItem",
                "resumeSavingProfile");

            if (!string.IsNullOrEmpty(user))
            {
                User = JsonSerializer.Deserialize<Profile>(user);
            }
        }
    }

    public async Task OnSaveAsync()
    {
        var http = new HttpClient();
        http.BaseAddress = new Uri(Navigation.BaseUri);

        var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";

        var tokenResult = await TokenProvider.RequestAccessToken(
            new AccessTokenRequestOptions
            {
                ReturnUrl = resumeUri
            });

        if (tokenResult.TryGetToken(out var token))
        {
            http.DefaultRequestHeaders.Add("Authorization", 
                $"Bearer {token.Value}");
            await http.PostAsJsonAsync("Save", User);
        }
        else
        {
            await JS.InvokeVoidAsync("sessionStorage.setItem", 
                "resumeSavingProfile", JsonSerializer.Serialize(User));
            Navigation.NavigateTo(tokenResult.InteractiveRequestUrl);
        }
    }

    public class Profile
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
    }
}

Salvare lo stato dell'app prima di un'operazione di autenticazione con l'archiviazione di sessione e un contenitore di stato

Durante un'operazione di autenticazione, esistono casi in cui si vuole salvare lo stato dell'app prima che il browser venga reindirizzato all'INDIRIZZO IP. Ciò può verificarsi quando si usa un contenitore di stato e si vuole ripristinare lo stato dopo l'esito positivo dell'autenticazione. È possibile usare un oggetto stato di autenticazione personalizzato per mantenere lo stato specifico dell'app o un riferimento a esso e ripristinare tale stato al termine dell'operazione di autenticazione. Nell'esempio seguente viene illustrato l'approccio .

Nell'app viene creata una classe contenitore di stato con proprietà che contengono i valori di stato dell'app. Nell'esempio seguente il contenitore viene usato per mantenere il valore del contatore del componente del modello di progetto predefinito Blazor (Counter.razor).Counter I metodi per la serializzazione e la deserializzazione del contenitore sono basati su System.Text.Json.

using System.Text.Json;

public class StateContainer
{
    public int CounterValue { get; set; }

    public string GetStateForLocalStorage()
    {
        return JsonSerializer.Serialize(this);
    }

    public void SetStateFromLocalStorage(string locallyStoredState)
    {
        var deserializedState = 
            JsonSerializer.Deserialize<StateContainer>(locallyStoredState);

        CounterValue = deserializedState.CounterValue;
    }
}

Il Counter componente usa il contenitore di stato per mantenere il currentCount valore esterno al componente:

@page "/counter"
@inject StateContainer State

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    protected override void OnInitialized()
    {
        if (State.CounterValue > 0)
        {
            currentCount = State.CounterValue;
        }
    }

    private void IncrementCount()
    {
        currentCount++;
        State.CounterValue = currentCount;
    }
}

Creare un oggetto ApplicationAuthenticationState da RemoteAuthenticationState. Specificare una Id proprietà che funge da identificatore per lo stato archiviato in locale.

ApplicationAuthenticationState.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState
{
    public string? Id { get; set; }
}
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState
{
    public string Id { get; set; }
}

Il Authentication componente (Authentication.razor) salva e ripristina lo stato dell'app usando l'archiviazione sessione locale con i StateContainer metodi GetStateForLocalStorage di serializzazione e deserializzazione e SetStateFromLocalStorage:

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IJSRuntime JS
@inject StateContainer State

<RemoteAuthenticatorViewCore Action="Action"
                             TAuthenticationState="ApplicationAuthenticationState"
                             AuthenticationState="AuthenticationState"
                             OnLogInSucceeded="RestoreState"
                             OnLogOutSucceeded="RestoreState" />

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

    public ApplicationAuthenticationState AuthenticationState { get; set; } =
        new ApplicationAuthenticationState();

    protected override async Task OnInitializedAsync()
    {
        if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
            Action) ||
            RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
            Action))
        {
            AuthenticationState.Id = Guid.NewGuid().ToString();

            await JS.InvokeVoidAsync("sessionStorage.setItem",
                AuthenticationState.Id, State.GetStateForLocalStorage());
        }
    }

    private async Task RestoreState(ApplicationAuthenticationState state)
    {
        if (state.Id != null)
        {
            var locallyStoredState = await JS.InvokeAsync<string>(
                "sessionStorage.getItem", state.Id);

            if (locallyStoredState != null)
            {
                State.SetStateFromLocalStorage(locallyStoredState);
                await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
            }
        }
    }
}
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IJSRuntime JS
@inject StateContainer State

<RemoteAuthenticatorViewCore Action="Action"
                             TAuthenticationState="ApplicationAuthenticationState"
                             AuthenticationState="AuthenticationState"
                             OnLogInSucceeded="RestoreState"
                             OnLogOutSucceeded="RestoreState" />

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

    public ApplicationAuthenticationState AuthenticationState { get; set; } =
        new ApplicationAuthenticationState();

    protected override async Task OnInitializedAsync()
    {
        if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
            Action) ||
            RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
            Action))
        {
            AuthenticationState.Id = Guid.NewGuid().ToString();

            await JS.InvokeVoidAsync("sessionStorage.setItem",
                AuthenticationState.Id, State.GetStateForLocalStorage());
        }
    }

    private async Task RestoreState(ApplicationAuthenticationState state)
    {
        if (state.Id != null)
        {
            var locallyStoredState = await JS.InvokeAsync<string>(
                "sessionStorage.getItem", state.Id);

            if (locallyStoredState != null)
            {
                State.SetStateFromLocalStorage(locallyStoredState);
                await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
            }
        }
    }
}

In questo esempio viene usato Microsoft Entra (ME-ID) per l'autenticazione. Nel file Program:

  • ApplicationAuthenticationState è configurato come tipo Microsoft Authentication Library (MSAL). RemoteAuthenticationState
  • Il contenitore di stato è registrato nel contenitore del servizio.
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

Personalizzare le route dell'app

Per impostazione predefinita, la Microsoft.AspNetCore.Components.WebAssembly.Authentication libreria usa le route illustrate nella tabella seguente per rappresentare stati di autenticazione diversi.

Itinerario Scopo
authentication/login Attiva un'operazione di accesso.
authentication/login-callback Gestisce il risultato di qualsiasi operazione di accesso.
authentication/login-failed Visualizza i messaggi di errore quando l'operazione di accesso non riesce per qualche motivo.
authentication/logout Attiva un'operazione di disconnesso.
authentication/logout-callback Gestisce il risultato di un'operazione di disconnesso.
authentication/logout-failed Visualizza i messaggi di errore quando l'operazione di disconnesso non riesce per qualche motivo.
authentication/logged-out Indica che l'utente ha eseguito correttamente la disconnessione.
authentication/profile Attiva un'operazione per modificare il profilo utente.
authentication/register Attiva un'operazione per registrare un nuovo utente.

Le route illustrate nella tabella precedente sono configurabili tramite RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.AuthenticationPaths. Quando si impostano le opzioni per fornire route personalizzate, verificare che l'app disponga di una route che gestisce ogni percorso.

Nell'esempio seguente tutti i percorsi sono preceduti da /security.

Authentication componente (Authentication.razor):

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

<RemoteAuthenticatorView Action="Action" />

@code{
    [Parameter]
    public string? Action { get; set; }
}
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="Action" />

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

Nel file Program:

builder.Services.AddApiAuthorization(options => { 
    options.AuthenticationPaths.LogInPath = "security/login";
    options.AuthenticationPaths.LogInCallbackPath = "security/login-callback";
    options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
    options.AuthenticationPaths.LogOutPath = "security/logout";
    options.AuthenticationPaths.LogOutCallbackPath = "security/logout-callback";
    options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
    options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
    options.AuthenticationPaths.ProfilePath = "security/profile";
    options.AuthenticationPaths.RegisterPath = "security/register";
});

Se il requisito richiede percorsi completamente diversi, impostare le route come descritto in precedenza ed eseguire il RemoteAuthenticatorView rendering con un parametro di azione esplicito:

@page "/register"

<RemoteAuthenticatorView Action="RemoteAuthenticationActions.Register" />

Se si sceglie di farlo, è possibile suddividere l'interfaccia utente in pagine diverse.

Personalizzare l'interfaccia utente di autenticazione

RemoteAuthenticatorView include un set predefinito di frammenti dell'interfaccia utente per ogni stato di autenticazione. Ogni stato può essere personalizzato passando un oggetto personalizzato RenderFragment. Per personalizzare il testo visualizzato durante il processo di accesso iniziale, è possibile modificare l'oggetto RemoteAuthenticatorView come indicato di seguito.

Authentication componente (Authentication.razor):

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

<RemoteAuthenticatorView Action="Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string? Action { get; set; }
}
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

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

Ha RemoteAuthenticatorView un frammento che può essere usato per ogni route di autenticazione illustrata nella tabella seguente.

Itinerario Frammento
authentication/login <LoggingIn>
authentication/login-callback <CompletingLoggingIn>
authentication/login-failed <LogInFailed>
authentication/logout <LogOut>
authentication/logout-callback <CompletingLogOut>
authentication/logout-failed <LogOutFailed>
authentication/logged-out <LogOutSucceeded>
authentication/profile <UserProfile>
authentication/register <Registering>

Personalizzare l'utente

Gli utenti associati all'app possono essere personalizzati.

Personalizzare l'utente con un'attestazione di payload

Nell'esempio seguente, gli utenti autenticati dell'app ricevono un'attestazione amr per ognuno dei metodi di autenticazione dell'utente. L'attestazione amr identifica il modo in cui l'oggetto del token è stato autenticato nelle attestazioni payload di Microsoft Identity Platform v1.0. Nell'esempio viene usata una classe di account utente personalizzata basata su RemoteUserAccount.

Creare una classe che estende la RemoteUserAccount classe . Nell'esempio seguente la AuthenticationMethod proprietà viene impostata sulla matrice di valori della amrJSproprietà ON dell'utente. AuthenticationMethod viene popolato automaticamente dal framework quando l'utente viene autenticato.

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[]? AuthenticationMethod { get; set; }
}
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[] AuthenticationMethod { get; set; }
}

Creare una factory che si estende AccountClaimsPrincipalFactory<TAccount> per creare attestazioni dai metodi di autenticazione dell'utente archiviati in CustomUserAccount.AuthenticationMethod:

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

public class CustomAccountFactory 
    : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomAccountFactory(NavigationManager navigation, 
        IAccessTokenProviderAccessor accessor) : base(accessor)
    {
    }

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

        if (initialUser.Identity != null && initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = (ClaimsIdentity)initialUser.Identity;

            if (account.AuthenticationMethod is not null)
            {
                foreach (var value in account.AuthenticationMethod)
                {
                    userIdentity.AddClaim(new Claim("amr", value));
                }
            }
        }

        return initialUser;
    }
}
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory 
    : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomAccountFactory(NavigationManager navigation, 
        IAccessTokenProviderAccessor accessor) : base(accessor)
    {
    }

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

        if (initialUser.Identity != null && initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = (ClaimsIdentity)initialUser.Identity;

            foreach (var value in account.AuthenticationMethod)
            {
                userIdentity.AddClaim(new Claim("amr", value));
            }
        }

        return initialUser;
    }
}

Registrare CustomAccountFactory per il provider di autenticazione in uso. Una delle registrazioni seguenti è valida:

  • AddOidcAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddOidcAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddMsalAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddMsalAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddApiAuthorization:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddApiAuthorization<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    

Gruppi e ruoli di sicurezza ME-ID con una classe di account utente personalizzata

Per un esempio aggiuntivo che funziona con i gruppi di sicurezza ME-ID e i ruoli Amministrazione istrator ME e una classe di account utente personalizzata, vedere ASP.NET Core Blazor WebAssembly con i gruppi e i ruoli di Microsoft Entra ID.

Prerendering con l'autenticazione

La pre-gestione del contenuto che richiede l'autenticazione e l'autorizzazione non è attualmente supportata. Dopo aver seguito le indicazioni in uno degli argomenti relativi all'app Blazor WebAssembly di sicurezza, usare le istruzioni seguenti per creare un'app che:

  • Prerenders percorsi per cui l'autorizzazione non è necessaria.
  • Non esegue il prerendere i percorsi per cui è necessaria l'autorizzazione.

Per il ClientProgram file del progetto, considerare le registrazioni del servizio comune in un metodo separato, ad esempio creare un ConfigureCommonServices metodo nel Client progetto. I servizi comuni sono quelli registrati dallo sviluppatore per l'uso da parte dei progetti client e server.

public static void ConfigureCommonServices(IServiceCollection services)
{
    services.Add...;
}

Nel file Program:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...

builder.Services.AddScoped( ... );

ConfigureCommonServices(builder.Services);

await builder.Build().RunAsync();

Server Nel file del Program progetto registrare i servizi aggiuntivi seguenti e chiamare ConfigureCommonServices:

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddRazorPages();
builder.Services.TryAddScoped<AuthenticationStateProvider, 
    ServerAuthenticationStateProvider>();

Client.Program.ConfigureCommonServices(services);

Server Nel metodo del Startup.ConfigureServices progetto registrare i servizi aggiuntivi seguenti e chiamare ConfigureCommonServices:

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddRazorPages();
    services.AddScoped<AuthenticationStateProvider, 
        ServerAuthenticationStateProvider>();
    services.AddScoped<SignOutSessionStateManager>();

    Client.Program.ConfigureCommonServices(services);
}

Per altre informazioni sul provider di autenticazione del Blazor server framework (ServerAuthenticationStateProvider), vedere ASP.NET'autenticazione e autorizzazione di baseBlazor.

Server Nel file del Pages/_Host.cshtml progetto sostituire l'helper Component tag (<component ... />) con quanto segue:

<div id="app">
    @if (HttpContext.Request.Path.StartsWithSegments("/authentication"))
    {
        <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
            render-mode="WebAssembly" />
    }
    else
    {
        <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
            render-mode="WebAssemblyPrerendered" />
    }
</div>

Nell'esempio precedente:

  • Il segnaposto {CLIENT APP ASSEMBLY NAME} è il nome dell'assembly dell'app client , ad esempio BlazorSample.Client.
  • Controllo condizionale per il segmento di /authentication percorso:
    • Evita il prerendering (render-mode="WebAssembly") per i percorsi di autenticazione.
    • Prerenders (render-mode="WebAssemblyPrerendered") per i percorsi non di autenticazione.

Opzioni per le app ospitate e i provider di accesso di terze parti

Quando si autentica e si autorizza un'app ospitata Blazor WebAssembly con un provider di terze parti, sono disponibili diverse opzioni per l'autenticazione dell'utente. Quello scelto dipende dallo scenario in uso.

Per altre informazioni, vedere Rendere persistenti attestazioni e token di provider esterni in ASP.NET Core.

Autenticare gli utenti per chiamare solo LE API di terze parti protette

Autenticare l'utente con un flusso OAuth sul lato client con il provider di API di terze parti:

builder.services.AddOidcAuthentication(options => { ... });

In questo scenario:

  • Il server che ospita l'app non ha un ruolo.
  • Le API nel server non possono essere protette.
  • L'app può chiamare solo API di terze parti protette.

Autenticare gli utenti con un provider di terze parti e chiamare API protette nel server host e nella terza parte

Configurare Identity con un provider di accesso di terze parti. Ottenere i token necessari per l'accesso alle API di terze parti e archiviarli.

Quando un utente accede, Identity raccoglie i token di accesso e aggiornamento come parte del processo di autenticazione. A questo punto, esistono due approcci disponibili per effettuare chiamate API a API di terze parti.

Usare un token di accesso al server per recuperare il token di accesso di terze parti

Usare il token di accesso generato nel server per recuperare il token di accesso di terze parti da un endpoint API server. Da qui usare il token di accesso di terze parti per chiamare le risorse API di terze parti direttamente dal Identity client.

Questo approccio non è consigliato. Questo approccio richiede il trattamento del token di accesso di terze parti come se fosse stato generato per un client pubblico. In termini OAuth, l'app pubblica non ha un segreto client perché non può essere considerata attendibile per archiviare i segreti in modo sicuro e il token di accesso viene generato per un client riservato. Un client riservato è un client che ha un segreto client e si presuppone che sia in grado di archiviare i segreti in modo sicuro.

  • Al token di accesso di terze parti potrebbero essere concessi ambiti aggiuntivi per eseguire operazioni sensibili in base al fatto che la terza parte ha emesso il token per un client più attendibile.
  • Analogamente, i token di aggiornamento non devono essere rilasciati a un client che non è attendibile, in quanto in questo modo concede al client l'accesso illimitato, a meno che non vengano applicate altre restrizioni.

Effettuare chiamate API dal client all'API server per chiamare API di terze parti

Effettuare una chiamata API dal client all'API server. Dal server recuperare il token di accesso per la risorsa API di terze parti ed eseguire qualsiasi chiamata necessaria.

Questo approccio è consigliato. Anche se questo approccio richiede un hop di rete aggiuntivo attraverso il server per chiamare un'API di terze parti, in definitiva comporta un'esperienza più sicura:

  • Il server può archiviare i token di aggiornamento e assicurarsi che l'app non perda l'accesso alle risorse di terze parti.
  • L'app non può perdere i token di accesso dal server che potrebbero contenere autorizzazioni più sensibili.

Usare endpoint OpenID Connessione (OIDC) v2.0

La libreria di autenticazione e Blazor i modelli di progetto usano endpoint OpenID Connessione (OIDC) v1.0. Per usare un endpoint v2.0, configurare l'opzione JWT Bearer JwtBearerOptions.Authority . Nell'esempio seguente, ME-ID viene configurato per la versione 2.0 aggiungendo un v2.0 segmento alla Authority proprietà :

using Microsoft.AspNetCore.Authentication.JwtBearer;

...

builder.Services.Configure<JwtBearerOptions>(
    JwtBearerDefaults.AuthenticationScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    });

In alternativa, l'impostazione può essere eseguita nel file delle impostazioni dell'app (appsettings.json):

{
  "Local": {
    "Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
    ...
  }
}

Se la descrizione di un segmento per l'autorità non è appropriata per il provider OIDC dell'app, ad esempio con provider non ME-ID, impostare direttamente la Authority proprietà. Impostare la proprietà in JwtBearerOptions o nel file di impostazioni dell'app (appsettings.json) con la Authority chiave .

L'elenco delle attestazioni nel token ID cambia per gli endpoint v2.0. La documentazione Microsoft sulle modifiche è stata ritirata, ma le indicazioni sulle attestazioni in un token ID sono disponibili nel riferimento alle attestazioni del token ID.

Configurare e usare gRPC nei componenti

Per configurare un'app Blazor WebAssembly per l'uso del framework ASP.NET Core gRPC:

Nota

La prerendering è abilitata per impostazione predefinita in Blazor App Web, quindi è necessario tenere conto del rendering del componente prima dal server e quindi dal client. Qualsiasi stato prerenderato deve essere propagato al client in modo che possa essere riutilizzato. Per altre informazioni, vedere Prerender ASP.NET Componenti di baseRazor.

Nota

Il prerendering è abilitato per impostazione predefinita nelle app ospitate Blazor WebAssembly , quindi è necessario tenere conto del rendering del componente prima dal server e quindi dal client. Qualsiasi stato prerenderato deve essere propagato al client in modo che possa essere riutilizzato. Per altre informazioni, vedere Prerender and integrate ASP.NET Core components .For more information, see Prerender and integrate ASP.NET Core Razor components.

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;

...

builder.Services.AddScoped(sp =>
{
    var baseAddressMessageHandler = 
        sp.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
    baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
    var grpcWebHandler = 
        new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
    var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress, 
        new GrpcChannelOptions { HttpHandler = grpcWebHandler });

    return new Greeter.GreeterClient(channel);
});

Un componente nell'app client può effettuare chiamate gRPC usando il client gRPC (Grpc.razor):

@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient

<h1>Invoke gRPC service</h1>

<p>
    <input @bind="name" placeholder="Type your name" />
    <button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
</p>

Server response: <strong>@serverResponse</strong>

@code {
    private string name = "Bert";
    private string? serverResponse;

    private async Task GetGreeting()
    {
        try
        {
            var request = new HelloRequest { Name = name };
            var reply = await GreeterClient.SayHelloAsync(request);
            serverResponse = reply.Message;
        }
        catch (Grpc.Core.RpcException ex)
            when (ex.Status.DebugException is 
                AccessTokenNotAvailableException tokenEx)
        {
            tokenEx.Redirect();
        }
    }
}

Per usare la proprietà, usare Grpc.Net.Client la Status.DebugException versione 2.30.0 o successiva.

Per altre informazioni, vedere gRPC-Web in ASP.NET app gRPC core.

Sostituire l'implementazione AuthenticationService

Le sottosezioni seguenti illustrano come sostituire:

  • Qualsiasi implementazione di JavaScript AuthenticationService .
  • Microsoft Authentication Library per JavaScript (MSAL.js).

Sostituire qualsiasi implementazione javaScript AuthenticationService

Creare una libreria JavaScript per gestire i dettagli di autenticazione personalizzati.

Avviso

Le indicazioni contenute in questa sezione sono un dettaglio dell'implementazione dell'oggetto predefinito RemoteAuthenticationService<TRemoteAuthenticationState,TAccount,TProviderOptions>. Il codice TypeScript in questa sezione si applica in modo specifico a ASP.NET Core in .NET 7 ed è soggetto a modifiche senza preavviso nelle prossime versioni di ASP.NET Core.

// .NET makes calls to an AuthenticationService object in the Window.
declare global {
  interface Window { AuthenticationService: AuthenticationService }
}

export interface AuthenticationService {
  // Init is called to initialize the AuthenticationService.
  public static init(settings: UserManagerSettings & AuthorizeServiceSettings, logger: any) : Promise<void>;

  // Gets the currently authenticated user.
  public static getUser() : Promise<{[key: string] : string }>;

  // Tries to get an access token silently.
  public static getAccessToken(options: AccessTokenRequestOptions) : Promise<AccessTokenResult>;

  // Tries to sign in the user or get an access token interactively.
  public static signIn(context: AuthenticationContext) : Promise<AuthenticationResult>;

  // Handles the sign-in process when a redirect is used.
  public static async completeSignIn(url: string) : Promise<AuthenticationResult>;

  // Signs the user out.
  public static signOut(context: AuthenticationContext) : Promise<AuthenticationResult>;

  // Handles the signout callback when a redirect is used.
  public static async completeSignOut(url: string) : Promise<AuthenticationResult>;
}

// The rest of these interfaces match their C# definitions.

export interface AccessTokenRequestOptions {
  scopes: string[];
  returnUrl: string;
}

export interface AccessTokenResult {
  status: AccessTokenResultStatus;
  token?: AccessToken;
}

export interface AccessToken {
  value: string;
  expires: Date;
  grantedScopes: string[];
}

export enum AccessTokenResultStatus {
  Success = 'Success',
  RequiresRedirect = 'RequiresRedirect'
}

export enum AuthenticationResultStatus {
  Redirect = 'Redirect',
  Success = 'Success',
  Failure = 'Failure',
  OperationCompleted = 'OperationCompleted'
};

export interface AuthenticationResult {
  status: AuthenticationResultStatus;
  state?: unknown;
  message?: string;
}

export interface AuthenticationContext {
  state?: unknown;
  interactiveRequest: InteractiveAuthenticationRequest;
}

export interface InteractiveAuthenticationRequest {
  scopes?: string[];
  additionalRequestParameters?: { [key: string]: any };
};

È possibile importare la libreria rimuovendo il tag originale <script> e aggiungendo un <script> tag che carica la libreria personalizzata. Nell'esempio seguente viene illustrato come sostituire il tag predefinito con uno che carica una libreria denominata <script>CustomAuthenticationService.js dalla wwwroot/js cartella .

In wwwroot/index.html prima dello Blazor script (_framework/blazor.webassembly.js) all'interno del tag di chiusura </body> :

- <script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>
+ <script src="js/CustomAuthenticationService.js"></script>

Per altre informazioni, vedere AuthenticationService.ts nel dotnet/aspnetcore repository GitHub.

Nota

I collegamenti della documentazione all'origine del riferimento .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo corrente per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare l'elenco a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

Sostituire Microsoft Authentication Library per JavaScript (MSAL.js)

Se un'app richiede una versione personalizzata di Microsoft Authentication Library per JavaScript (MSAL.js),seguire questa procedura:

  1. Verificare che il sistema disponga della versione più recente di .NET SDK per sviluppatori o di ottenere e installare l'SDK per sviluppatori più recente da .NET Core SDK: programmi di installazione e file binari. La configurazione dei feed NuGet interni non è necessaria per questo scenario.
  2. Configurare il dotnet/aspnetcore repository GitHub per lo sviluppo seguendo la documentazione disponibile in Build ASP.NET Core from Source ( Build ASP.NET Core from Source). Creare una copia tramite fork e clonare o scaricare un archivio ZIP del dotnet/aspnetcore repository GitHub.
  3. Aprire il src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json file e impostare la versione desiderata di @azure/msal-browser. Per un elenco delle versioni rilasciate, visitare il @azure/msal-browser sito Web npm e selezionare la scheda Versioni .
  4. Compilare il Authentication.Msal progetto nella src/Components/WebAssembly/Authentication.Msal/src cartella con il yarn build comando in una shell dei comandi.
  5. Se l'app usa asset compressi (Brotli/Gzip), comprimere il Interop/dist/Release/AuthenticationService.js file.
  6. Copiare il AuthenticationService.js file e le versioni compresse (.br.gz/) del file, se prodotto, dalla Interop/dist/Release cartella nella cartella dell'app publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal negli asset pubblicati dell'app.

Passare le opzioni del provider personalizzato

Definire una classe per passare i dati alla libreria JavaScript sottostante.

Importante

La struttura della classe deve corrispondere a quanto previsto dalla libreria quando on JSviene serializzato con System.Text.Json.

L'esempio seguente illustra una ProviderOptions classe con JsonPropertyName attributi corrispondenti alle aspettative di una libreria di provider personalizzata ipotetica:

public class ProviderOptions
{
    public string? Authority { get; set; }
    public string? MetadataUrl { get; set; }

    [JsonPropertyName("client_id")]
    public string? ClientId { get; set; }

    public IList<string> DefaultScopes { get; } = 
        new List<string> { "openid", "profile" };

    [JsonPropertyName("redirect_uri")]
    public string? RedirectUri { get; set; }

    [JsonPropertyName("post_logout_redirect_uri")]
    public string? PostLogoutRedirectUri { get; set; }

    [JsonPropertyName("response_type")]
    public string? ResponseType { get; set; }

    [JsonPropertyName("response_mode")]
    public string? ResponseMode { get; set; }
}
public class ProviderOptions
{
    public string Authority { get; set; }
    public string MetadataUrl { get; set; }

    [JsonPropertyName("client_id")]
    public string ClientId { get; set; }

    public IList<string> DefaultScopes { get; } = 
        new List<string> { "openid", "profile" };

    [JsonPropertyName("redirect_uri")]
    public string RedirectUri { get; set; }

    [JsonPropertyName("post_logout_redirect_uri")]
    public string PostLogoutRedirectUri { get; set; }

    [JsonPropertyName("response_type")]
    public string ResponseType { get; set; }

    [JsonPropertyName("response_mode")]
    public string ResponseMode { get; set; }
}

Registrare le opzioni del provider nel sistema di inserimento delle dipendenze e configurare i valori appropriati:

builder.Services.AddRemoteAuthentication<RemoteAuthenticationState, RemoteUserAccount,
    ProviderOptions>(options => {
        options.Authority = "...";
        options.MetadataUrl = "...";
        options.ClientId = "...";
        options.DefaultScopes = new List<string> { "openid", "profile", "myApi" };
        options.RedirectUri = "https://localhost:5001/authentication/login-callback";
        options.PostLogoutRedirectUri = "https://localhost:5001/authentication/logout-callback";
        options.ResponseType = "...";
        options.ResponseMode = "...";
    });

L'esempio precedente imposta gli URI di reindirizzamento con valori letterali stringa regolari. Sono disponibili le alternative seguenti:

  • TryCreate using IWebAssemblyHostEnvironment.BaseAddress:

    Uri.TryCreate(
        $"{builder.HostEnvironment.BaseAddress}authentication/login-callback", 
        UriKind.Absolute, out var redirectUri);
    options.RedirectUri = redirectUri;
    
  • Configurazione del generatore host:

    options.RedirectUri = builder.Configuration["RedirectUri"];
    

    wwwroot/appsettings.json:

    {
      "RedirectUri": "https://localhost:5001/authentication/login-callback"
    }
    

Risorse aggiuntive