Zusätzliche Sicherheitsszenarios für ASP.NET Core Blazor WebAssembly

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

In diesem Artikel werden zusätzliche Sicherheitsszenarios für Blazor WebAssembly-Apps beschrieben.

Anfügen von Token an ausgehende Anforderungen

AuthorizationMessageHandler ist ein DelegatingHandler zum Verarbeiten von Zugriffstoken. Token werden mithilfe des IAccessTokenProvider Diensts abgerufen, der vom Framework registriert wird. Wenn ein Token nicht abgerufen werden kann, wird eine AccessTokenNotAvailableException ausgelöst. AccessTokenNotAvailableException verfügt über eine Redirect-Methode, die mithilfe der angegebenen AccessTokenResult.InteractionOptions zur AccessTokenResult.InteractiveRequestUrl navigiert, um das Zugriffstoken zu aktualisieren.

Zur Vereinfachung stellt das Framework BaseAddressAuthorizationMessageHandler bereit, der mit der Basisadresse der App als autorisierte URL vorkonfiguriert ist. Zugriffstoken werden nur hinzugefügt, wenn der Anforderungs-URI innerhalb des Basis-URI der APP liegt. Wenn sich ausgehende Anforderungs-URIs nicht innerhalb des Basis-URI der App befinden, verwenden Sie eine benutzerdefinierte AuthorizationMessageHandler-Klasse (empfohlen), oder konfigurieren Sie AuthorizationMessageHandler.

Hinweis

Zusätzlich zur Client-App-Konfiguration für den Server-API-Zugriff muss die Server-API auch Cross-Origin-Anforderungen (CORS) zulassen, wenn sich der Client und der Server nicht an derselben Basisadresse befinden. Weitere Informationen zur serverseitigen CORS-Konfiguration finden Sie im Abschnitt Cross-Origin Resource Sharing (CORS) weiter unten in diesem Artikel.

Siehe folgendes Beispiel:

Im folgenden Beispiel ist HttpClientFactoryServiceCollectionExtensions.AddHttpClient eine Erweiterung in Microsoft.Extensions.Http. Fügen Sie das Paket einer App hinzu, die noch nicht darauf verweist.

Hinweis

Einen Leitfaden zum Hinzufügen von Paketen zu .NET-Apps finden Sie in Installieren und Verwalten von Paketen unter Workflow der Nutzung von Paketen (NuGet-Dokumentation). Überprüfen Sie unter NuGet.org, ob die richtige Paketversion verwendet wird.

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

Für eine gehostete Blazor-Projektmappe, die auf der Blazor WebAssembly-Projektvorlage basiert, befinden sich die Anforderungs-URIs standardmäßig im Basis-URI der App. Daher wird IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) der HttpClient.BaseAddress in einer App zugewiesen, die aus der Projektvorlage generiert wurde.

Der konfigurierte HttpClient wird verwendet, um autorisierte Anforderungen mithilfe des try-catch-Musters zu stellen:

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

Szenarios für benutzerdefinierte Authentifizierungsanforderungen

In den folgenden Szenarios wird veranschaulicht, wie Sie Authentifizierungsanforderungen anpassen und den Anmeldepfad aus den Authentifizierungsoptionen abrufen.

Anpassen des Anmeldevorgangs

Verwalten Sie zusätzliche Parameter für eine Anmeldeanforderung mit den folgenden Methoden, einmal oder mehrmals für eine neue Instanz von InteractiveRequestOptions:

Im folgenden Beispiel für eine LoginDisplay-Komponente werden der Anmeldeanforderung zusätzliche Parameter hinzugefügt:

  • prompt ist auf login festgelegt: Erzwingt, dass Benutzer ihre Anmeldeinformationen für diese Anforderung eingeben, das einmalige Anmelden wird negiert.
  • loginHint ist auf peter@contoso.com festgelegt: Füllt das Feld „Benutzername/E-Mail-Adresse“ der Anmeldeseite für den Benutzer vorab als peter@contoso.com aus. Apps verwenden diesen Parameter häufig für die erneute Authentifizierung, nachdem der Benutzername mithilfe des Anspruchs preferred_username aus einer vorherigen Anmeldung extrahiert wurde.

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

Weitere Informationen finden Sie in den folgenden Ressourcen:

Anpassen von Optionen vor dem interaktiven Abrufen eines Tokens

Wenn eine AccessTokenNotAvailableException auftritt, verwalten Sie zusätzliche Parameter für eine neue Zugriffstokenanforderung des Identitätsanbieters mit den folgenden Methoden, einmal oder mehrmals für eine neue Instanz von InteractiveRequestOptions:

Im folgenden Beispiel, das JSON-Daten über die Web-API abruft, werden der Umleitungsanforderung zusätzliche Parameter hinzugefügt, wenn kein Zugriffstoken verfügbar ist (AccessTokenNotAvailableException wird ausgelöst):

  • prompt ist auf login festgelegt: Erzwingt, dass Benutzer ihre Anmeldeinformationen für diese Anforderung eingeben, das einmalige Anmelden wird negiert.
  • loginHint ist auf peter@contoso.com festgelegt: Füllt das Feld „Benutzername/E-Mail-Adresse“ der Anmeldeseite für den Benutzer vorab als peter@contoso.com aus. Apps verwenden diesen Parameter häufig für die erneute Authentifizierung, nachdem der Benutzername mithilfe des Anspruchs preferred_username aus einer vorherigen Anmeldung extrahiert wurde.
try
{
    var examples = await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

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

Im vorherigen Beispiel wird Folgendes angenommen:

Weitere Informationen finden Sie in den folgenden Ressourcen:

Anpassen von Optionen bei Verwendung eines IAccessTokenProvider

Wenn bei Verwendung einer IAccessTokenProvider-Instanz kein Token abgerufen werden kann, verwalten Sie zusätzliche Parameter für eine neue Zugriffstokenanforderung des Identitätsanbieters mit den folgenden Methoden, einmal oder mehrmals für eine neue Instanz von InteractiveRequestOptions:

Das folgende Beispiel versucht, ein Zugriffstoken für den Benutzer abzurufen. Dabei werden der Anmeldeanforderung zusätzliche Parameter hinzugefügt, wenn beim Aufruf von TryGetToken kein Token abgerufen werden kann:

  • prompt ist auf login festgelegt: Erzwingt, dass Benutzer ihre Anmeldeinformationen für diese Anforderung eingeben, das einmalige Anmelden wird negiert.
  • loginHint ist auf peter@contoso.com festgelegt: Füllt das Feld „Benutzername/E-Mail-Adresse“ der Anmeldeseite für den Benutzer vorab als peter@contoso.com aus. Apps verwenden diesen Parameter häufig für die erneute Authentifizierung, nachdem der Benutzername mithilfe des Anspruchs preferred_username aus einer vorherigen Anmeldung extrahiert wurde.
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);
}

Im vorherigen Beispiel wird Folgendes angenommen:

Weitere Informationen finden Sie in den folgenden Ressourcen:

Abmelden mit einer benutzerdefinierten Rückgabe-URL

Im folgenden Beispiel wird der oder die Benutzer*n abgemeldet und an den /goodbye-Endpunkt weitergeleitet:

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

Abrufen des Anmeldepfads aus den Authentifizierungsoptionen

Abrufen des konfigurierten Anmeldepfads aus den RemoteAuthenticationOptions:

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

Im vorherigen Beispiel wird Folgendes angenommen:

Benutzerdefinierte AuthorizationMessageHandler-Klasse

Diese Anleitung in diesem Abschnitt wird für Client-Apps empfohlen, die ausgehende Anforderungen an URIs vornehmen, die sich nicht im Basis-URI der App befinden.

Im folgenden Beispiel erweitert eine benutzerdefinierte-Klasse AuthorizationMessageHandler für die Verwendung als DelegatingHandler für einen HttpClient. ConfigureHandler konfiguriert diesen Handler, um ausgehende HTTP-Anforderungen mithilfe eines Zugriffstokens zu autorisieren. Das Zugriffstoken wird nur angefügt, wenn mindestens eine der autorisierten URLs eine Basis des Anforderungs-URIs (HttpRequestMessage.RequestUri) ist.

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

Im vorherigen Code sind die Bereiche example.read und example.write allgemeine Beispiele, die keine gültigen Bereiche für einen bestimmten Anbieter widerspiegeln sollen.

In der Program-Datei ist CustomAuthorizationMessageHandler als vorübergehender Dienst registriert und wird als DelegatingHandler für ausgehende HttpResponseMessage-Instanzen konfiguriert, die von einem benannten HttpClient erstellt werden.

Im folgenden Beispiel ist HttpClientFactoryServiceCollectionExtensions.AddHttpClient eine Erweiterung in Microsoft.Extensions.Http. Fügen Sie das Paket einer App hinzu, die noch nicht darauf verweist.

Hinweis

Einen Leitfaden zum Hinzufügen von Paketen zu .NET-Apps finden Sie in Installieren und Verwalten von Paketen unter Workflow der Nutzung von Paketen (NuGet-Dokumentation). Überprüfen Sie unter NuGet.org, ob die richtige Paketversion verwendet wird.

builder.Services.AddTransient<CustomAuthorizationMessageHandler>();

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

Hinweis

Im vorherigen Beispiel wird der CustomAuthorizationMessageHandlerDelegatingHandler als vorübergehender Dienst für AddHttpMessageHandler registriert. Die vorübergehende Registrierung wird für IHttpClientFactory empfohlen, da sie eigene DI-Bereiche verwaltet. Weitere Informationen finden Sie in den folgenden Ressourcen:

Bei einer gehosteten Blazor-Lösung, die auf der Blazor WebAssembly-Projektvorlage basiert, wird IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) standardmäßig HttpClient.BaseAddress zugewiesen.

Der konfigurierte HttpClient wird verwendet, um autorisierte Anforderungen mithilfe des try-catch-Musters zu stellen: Wenn der Client mit CreateClient erstellt wird (Microsoft.Extensions.Http-Paket), wird HttpClient für Instanzen bereitgestellt, die Zugriffstoken enthalten, wenn Anforderungen an die Server-API vorgenommen werden. Wenn der Anforderungs-URI wie im folgenden Beispiel (ExampleAPIMethod) ein relativer URI ist, wird er mit der BaseAddress kombiniert, wenn die Client-App die Anforderung vornimmt:

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

Konfigurieren von AuthorizationMessageHandler

AuthorizationMessageHandler kann mithilfe der Methode ConfigureHandler mit autorisierten URLs, Bereichen und einer Rückgabe-URL konfiguriert werden. ConfigureHandler konfiguriert den Handler, um ausgehende HTTP-Anforderungen mithilfe eines Zugriffstokens zu autorisieren. Das Zugriffstoken wird nur angefügt, wenn mindestens eine der autorisierten URLs eine Basis des Anforderungs-URIs (HttpRequestMessage.RequestUri) ist. Wenn der Anforderungs-URI ein relativer URI ist, wird er mit der BaseAddress kombiniert.

Im folgenden Beispiel konfiguriert AuthorizationMessageHandler einen HttpClient in der Program-Datei:

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

Im vorherigen Code sind die Bereiche example.read und example.write allgemeine Beispiele, die keine gültigen Bereiche für einen bestimmten Anbieter widerspiegeln sollen.

Bei einer gehosteten Blazor-Lösung, die auf der Blazor WebAssembly-Projektvorlage basiert, wird IWebAssemblyHostEnvironment.BaseAddress standardmäßig Folgendem zugewiesen:

  • Der HttpClient.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)).
  • Einer URL des authorizedUrls-Arrays.

Typisierter HttpClient

Es kann ein typisierter Client definiert werden, der alle Probleme mit dem HTTP- und Tokenabruf innerhalb einer einzelnen Klasse bearbeitet.

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

Im vorherigen Beispiel ist der WeatherForecast-Typ eine statische Klasse, die Wettervorhersagedaten enthält. Der Platzhalter {ASSEMBLY NAME} ist der Assemblyname der App (z. B. using static BlazorSample.Data;).

Im folgenden Beispiel ist HttpClientFactoryServiceCollectionExtensions.AddHttpClient eine Erweiterung in Microsoft.Extensions.Http. Fügen Sie das Paket einer App hinzu, die noch nicht darauf verweist.

Hinweis

Einen Leitfaden zum Hinzufügen von Paketen zu .NET-Apps finden Sie in Installieren und Verwalten von Paketen unter Workflow der Nutzung von Paketen (NuGet-Dokumentation). Überprüfen Sie unter NuGet.org, ob die richtige Paketversion verwendet wird.

In der Program-Datei:

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

Bei einer gehosteten Blazor-Lösung, die auf der Blazor WebAssembly-Projektvorlage basiert, wird IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) standardmäßig HttpClient.BaseAddress zugewiesen.

In einer Komponente, die Wetterdaten abruft:

@inject WeatherForecastClient Client

...

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

Konfigurieren des HttpClient-Handlers

Der Handler kann mit dem ConfigureHandler für ausgehende HTTP-Anforderungen weiter konfiguriert werden.

Im folgenden Beispiel ist HttpClientFactoryServiceCollectionExtensions.AddHttpClient eine Erweiterung in Microsoft.Extensions.Http. Fügen Sie das Paket einer App hinzu, die noch nicht darauf verweist.

Hinweis

Einen Leitfaden zum Hinzufügen von Paketen zu .NET-Apps finden Sie in Installieren und Verwalten von Paketen unter Workflow der Nutzung von Paketen (NuGet-Dokumentation). Überprüfen Sie unter NuGet.org, ob die richtige Paketversion verwendet wird.

In der Program-Datei:

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

Im vorherigen Code sind die Bereiche example.read und example.write allgemeine Beispiele, die keine gültigen Bereiche für einen bestimmten Anbieter widerspiegeln sollen.

Bei einer gehosteten Blazor-Lösung, die auf der Blazor WebAssembly-Projektvorlage basiert, wird IWebAssemblyHostEnvironment.BaseAddress standardmäßig Folgendem zugewiesen:

  • Der HttpClient.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)).
  • Einer URL des authorizedUrls-Arrays.

Nicht authentifizierte oder nicht autorisierte Web-API-Anforderungen in einer App mit einem sicheren Standardclient

Eine App, die normalerweise einen sicheren Standard-HttpClient verwendet, kann auch nicht authentifizierte oder nicht autorisierte Web-API-Anforderungen stellen, indem sie einen benannten HttpClient konfiguriert.

Im folgenden Beispiel ist HttpClientFactoryServiceCollectionExtensions.AddHttpClient eine Erweiterung in Microsoft.Extensions.Http. Fügen Sie das Paket einer App hinzu, die noch nicht darauf verweist.

Hinweis

Einen Leitfaden zum Hinzufügen von Paketen zu .NET-Apps finden Sie in Installieren und Verwalten von Paketen unter Workflow der Nutzung von Paketen (NuGet-Dokumentation). Überprüfen Sie unter NuGet.org, ob die richtige Paketversion verwendet wird.

In der Program-Datei:

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

Bei einer gehosteten Blazor-Lösung, die auf der Blazor WebAssembly-Projektvorlage basiert, wird IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) standardmäßig HttpClient.BaseAddress zugewiesen.

Die obenstehende Registrierung erfolgt zusätzlich zur bereits vorhandenen sicheren Standardregistrierung des HttpClient.

Eine Komponente erstellt den HttpClient aus der IHttpClientFactory (Microsoft.Extensions.Http-Paket), um nicht authentifizierte oder nicht autorisierte Anforderungen zu stellen:

@inject IHttpClientFactory ClientFactory

...

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

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

        ...
    }
}

Hinweis

Der Controller in der Server-API (ExampleNoAuthenticationController) für das obenstehende Beispiel ist nicht mit dem [Authorize]-Attribut gekennzeichnet.

Die Entscheidung, ob ein sicherer Client oder ein unsicherer Client als Standard-HttpClient-Instanz verwendet werden soll, trifft der Entwickler. Eine Möglichkeit, diese Entscheidung zu treffen, ist die Berücksichtigung der Anzahl der authentifizierten und nicht authentifizierten Endpunkte, die von der App kontaktiert werden. Wenn die Mehrzahl der Anforderungen der App zum Sichern von API-Endpunkten verwendet wird, verwenden Sie die authentifizierte HttpClient-Instanz als Standard. Registrieren Sie andernfalls die nicht authentifizierte HttpClient-Instanz als Standard.

Ein alternativer Ansatz für die Verwendung der IHttpClientFactory ist das Erstellen eines typisierten Clients für nicht authentifizierten Zugriff auf anonyme Endpunkte.

Anfordern zusätzlicher Zugriffstoken

Zugriffstoken können mithilfe von IAccessTokenProvider.RequestAccessToken manuell abgerufen werden. Im folgenden Beispiel benötigt eine App einen zusätzlichen Bereich für den standardmäßigen HttpClient. In dem Beispiel zu Microsoft Authentication Library (MSAL) wird der Bereich mit MsalProviderOptions konfiguriert:

In der Program-Datei:

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

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

Bei den Platzhaltern {CUSTOM SCOPE 1} und {CUSTOM SCOPE 2} im obigen Beispiel handelt es sich um benutzerdefinierte Bereiche.

Hinweis

AdditionalScopesToConsent ist nicht in der Lage, über die Zustimmungsoberfläche von Microsoft Entra ID delegierte Benutzerberechtigungen für Microsoft Graph bereitzustellen, wenn ein Benutzer zum ersten Mal eine App verwendet, die in Microsoft Azure registriert ist. Weitere Informationen finden Sie unter Verwenden der Graph-API mit ASP.NET CoreBlazor WebAssembly.

Die Methode IAccessTokenProvider.RequestAccessToken stellt eine Überladung bereit, mit der Apps Zugriffstoken mit vorgegebenen Bereichen bereitstellen kann.

In einer Razor-Komponente:

@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))
{
    ...
}

Bei den Platzhaltern {CUSTOM SCOPE 1} und {CUSTOM SCOPE 2} im obigen Beispiel handelt es sich um benutzerdefinierte Bereiche.

AccessTokenResult.TryGetToken gibt Folgendes zurück:

  • true mit dem token zur Verwendung
  • false, wenn das Token nicht abgerufen wird

Ressourcenfreigabe zwischen verschiedenen Ursprüngen (Cross-Origin Resource Sharing, CORS)

Beim Senden von Anmeldeinformationen (Autorisierungscookies/-header) bei CORS-Anforderungen muss der Authorization-Header von der CORS-Richtlinie zugelassen werden.

Die folgende Richtlinie enthält die Konfiguration für:

  • Anforderungsursprünge (http://localhost:5000, https://localhost:5001).
  • Any-Methode (Verb).
  • Content-Type- und Authorization-Header. Um einen benutzerdefinierten Header (z. B. x-custom-header) zuzulassen, listen Sie den Header beim Aufrufen von WithHeaders auf.
  • Anmeldeinformationen werden durch clientseitigen JavaScript-Code festgelegt (credentials-Eigenschaft auf include festgelegt).
app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
        .AllowAnyMethod()
        .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, 
            "x-custom-header")
        .AllowCredentials());

Eine gehostete Blazor-Lösung, die auf der Blazor WebAssembly-Projektvorlage basiert, verwendet für die Client- und Server-Apps die gleiche Basisadresse. Die HttpClient.BaseAddress der Client-App wird standardmäßig auf einen URI builder.HostEnvironment.BaseAddress festgelegt. Die CORS-Konfiguration ist in der Standardkonfiguration einer gehosteten Blazor-Lösung nicht erforderlich. Zusätzliche Client-Apps, die nicht vom Serverprojekt gehostet werden und nicht die Basisadresse der Server-App teilen, erfordern CORS-Konfiguration im Serverprojekt.

Weitere Informationen finden Sie unter Aktivieren von CORS in ASP.NET Core und in der HTTP-Anforderungstesterkomponente (Components/HTTPRequestTester.razor) der Beispiel-App.

Behandeln von Fehlern mit Tokenanforderungen

Wenn eine Single-Page-Anwendung (Single Page Application, SPA) eine*n Benutzer*in mithilfe von Open ID Connect (OICD) authentifiziert, wird der Authentifizierungsstatus lokal innerhalb der SPA und im Identity-Anbieter (Identity Provider, IP) in Form eines Sitzungscookies verwaltet, das festgelegt wird, wenn der Benutzer seine Anmeldeinformationen bereitstellt.

Die Token, die der IP für den Benutzer ausgibt, sind in der Regel nur für kurze Zeit (etwa eine Stunde) gültig, sodass die Client-App regelmäßig neue Token abrufen muss. Andernfalls wird der Benutzer nach Ablauf der ausgegebenen Token abgemeldet. In den meisten Fällen sind die OICD-Clients in der Lage, neue Token bereitzustellen, ohne dass sich der Benutzer noch mal authentifizieren muss, weil ein Authentifizierungsstatus oder eine Sitzung im IP gespeichert ist.

Manchmal kann der Client ohne Benutzerinteraktion keine Token abrufen. Dies ist zum Beispiel dann der Fall, wenn sich der Benutzer aus einem beliebigen Grund explizit vom IP abmeldet. Dies passiert, wenn der Benutzer https://login.microsoftonline.com aufruft und sich abmeldet. In diesen Szenarios weiß die App nicht sofort, dass sich der Benutzer abgemeldet hat. Alle Token, die im Client enthalten sind, sind dann möglicherweise nicht mehr gültig. Außerdem kann der Client keine neuen Token ohne Benutzerinteraktion bereitstellen, nachdem das aktuelle Token abgelaufen ist.

Diese Szenarios sind nicht spezifisch für die tokenbasierte Authentifizierung. Sie gehören bei SPAs dazu. Wenn eine SPA, die cookies verwendet, eine Server-API aufruft, wird ein Fehler ausgelöst, wenn das Authentifizierungscookie entfernt wird.

Wenn eine App API-Aufrufe für geschützte Ressourcen ausführt, müssen Sie Folgendes beachten:

  • Damit ein neues Zugriffstoken zum Aufrufen der API bereitgestellt werden kann, muss der Benutzer sich möglicherweise noch einmal authentifizieren.
  • Auch wenn der Client über ein Token verfügt, das anscheinend gültig ist, kann der Serveraufruf fehlschlagen, weil das Token vom Benutzer widerrufen wurde.

Wenn die App ein Token anfordert, gibt es zwei Möglichkeiten:

  • Die Anforderung ist erfolgreich, und die App verfügt über ein gültiges Token.
  • Die Anforderung schlägt fehl, und die App muss den Benutzer noch einmal authentifizieren, um ein neues Token zu erhalten.

Wenn eine Tokenanforderung fehlschlägt, müssen Sie entscheiden, ob Sie den aktuellen Zustand speichern möchten, bevor Sie eine Umleitung ausführen. Es gibt mehrere Ansätze zum Speichern des Zustands bei zunehmender Komplexität:

  • Speichern Sie den aktuellen Seitenzustand im Sitzungsspeicher. Überprüfen Sie während der OnInitializedAsync-Lebenszyklusmethode (OnInitializedAsync), ob der Zustand wiederhergestellt werden kann, bevor Sie fortfahren.
  • Fügen Sie einen Abfragezeichenfolgenparameter hinzu, und verwenden Sie diesen, um der App zu signalisieren, dass sie den zuvor gespeicherten Zustand aktualisieren muss.
  • Fügen Sie einen Abfragezeichenfolgenparameter mit einem eindeutigen Bezeichner hinzu, um Daten im Sitzungsspeicher zu speichern, ohne Konflikte mit anderen Elementen zu riskieren.

Speichern des App-Zustands vor einem Authentifizierungsvorgang mit Sitzungsspeicher

Das folgende Beispiel veranschaulicht die Vorgehensweise:

  • Speichern des Zustands vor der Umleitung auf die Anmeldeseite
  • Wiederherstellen des vorherigen Zustands nach der Authentifizierung mithilfe eines Abfragezeichenfolgenparameters
...
@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; }
    }
}

Speichern des App-Zustands vor einem Authentifizierungsvorgang mit Sitzungsspeicher und einem Zustandscontainer

Während eines Authentifizierungsvorgangs sollten Sie ggf. den App-Zustand speichern, bevor der Browser an den IP umgeleitet wird. Dies bietet sich zum Beispiel dann an, wenn Sie einen Zustandscontainer verwenden und den Zustand nach erfolgreicher Authentifizierung wiederherstellen möchten. Sie können ein benutzerdefiniertes Zustandsobjekt für die Authentifizierung verwenden, um den App-spezifischen Zustand oder einen Verweis auf diesen zu speichern und diesen Zustand nach erfolgreichem Abschluss des Authentifizierungsvorgangs wiederherzustellen. Im folgenden Beispiel wird dieser Ansatz veranschaulicht.

In der App wird eine Zustandscontainerklasse mit Eigenschaften erstellt, die die Zustandswerte der App enthalten. Im folgenden Beispiel wird der Container verwendet, um den Leistungsindikatorwert der Counter-Komponente der standardmäßigen Blazor-Projektvorlage (Counter.razor) beizubehalten. Methoden zum (De-)Serialisieren des Containers basierend auf 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;
    }
}

Die Komponente Counter verwendet den Zustandscontainer, um den Wert currentCount außerhalb der Komponente beizubehalten:

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

Erstellen Sie einen ApplicationAuthenticationState aus RemoteAuthenticationState. Stellen Sie eine Id-Eigenschaft bereit, die als Bezeichner für den lokal gespeicherten Zustand fungiert.

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

Die Komponente Authentication (Authentication.razor) speichert den Zustand der App mithilfe des lokalen Sitzungsspeichers über die StateContainer-(De-)Serialisierungsmethoden GetStateForLocalStorage und SetStateFromLocalStorage und stellt diesen auch darüber wieder her:

@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 diesem Beispiel wird Microsoft Entra ID (ME-ID) für die Authentifizierung verwendet. In der Program-Datei:

  • ApplicationAuthenticationState wird als RemoteAuthenticationState-Typ der Microsoft-Authentifizierungsbibliothek konfiguriert.
  • Der Zustandscontainer wird im Dienstcontainer registriert.
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

Anpassen von App-Routen

Standardmäßig verwendet die Bibliothek Microsoft.AspNetCore.Components.WebAssembly.Authentication die Routen, die in der folgenden Tabelle angezeigt werden, um unterschiedliche Authentifizierungszustände darzustellen.

Route Zweck
authentication/login Löst einen Anmeldevorgang aus
authentication/login-callback Verarbeitet das Ergebnis eines beliebigen Anmeldevorgangs.
authentication/login-failed Zeigt Fehlermeldungen an, wenn der Anmeldevorgang aus einem beliebigen Grund fehlschlägt
authentication/logout Löst einen Abmeldevorgang aus
authentication/logout-callback Verarbeitet das Ergebnis eines beliebigen Abmeldevorgangs
authentication/logout-failed Zeigt Fehlermeldungen an, wenn der Abmeldevorgang aus einem beliebigen Grund fehlschlägt
authentication/logged-out Gibt an, dass ein Benutzer sich erfolgreich abgemeldet hat
authentication/profile Löst einen Vorgang aus, um das Benutzerprofil zu bearbeiten
authentication/register Löst einen Vorgang aus, um einen neuen Benutzer zu registrieren

Die in der obigen Tabelle aufgeführten Routen können über RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.AuthenticationPaths konfiguriert werden. Wenn Sie Optionen zum Bereitstellen benutzerdefinierter Routen festlegen, vergewissern Sie sich, dass die App über eine Route verfügt, die die einzelnen Pfade verarbeitet.

Im folgenden Beispiel haben alle Pfade das Präfix /security.

Authentication-Komponente (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; }
}

In der Program-Datei:

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

Wenn vollkommen unterschiedliche Pfade verlangt werden, legen Sie die Routen wie zuvor beschrieben fest, und rendern Sie die RemoteAuthenticatorView mit einem expliziten Aktionsparameter:

@page "/register"

<RemoteAuthenticatorView Action="RemoteAuthenticationActions.Register" />

Wenn Sie sich hierfür entscheiden, können Sie die Benutzeroberfläche auf mehrere Seiten aufteilen.

Anpassen der Benutzeroberfläche für die Authentifizierung

RemoteAuthenticatorView enthält einen Standardsatz von Benutzeroberflächenfragmenten für alle Authentifizierungszustände. Jeder Zustand kann angepasst werden, indem ein benutzerdefiniertes RenderFragmentübergeben wird. Wenn Sie den angezeigten Text während des ersten Anmeldeprozesses anpassen möchten, können Sie die RemoteAuthenticatorView wie folgt ändern.

Authentication-Komponente (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; }
}

Die RemoteAuthenticatorView verfügt über ein Fragment, das für die Authentifizierungsrouten in der folgenden Tabelle verwendet werden kann.

Route Fragment
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>

Anpassen des Benutzers

Benutzer, die an die App gebunden sind, können angepasst werden.

Anpassen des Benutzers mit einem Nutzdatenanspruch

Im folgenden Beispiel erhalten die authentifizierten Benutzer der App einen amr-Anspruch für jede Authentifizierungsmethode des Benutzers. Der amr-Anspruch gibt an, wie der Antragsteller des Tokens in Microsoft Identity Platform v1.0-Nutzdatenansprüchen authentifiziert wurde. Im Beispiel wird eine benutzerdefinierte Benutzerkontoklasse basierend auf RemoteUserAccountverwendet.

Erstellen Sie eine Klasse, mit der die Klasse RemoteUserAccount erweitert wird: Im folgenden Beispiel wird die AuthenticationMethod-Eigenschaft auf das Array von amr-JSON-Eigenschaftswerten des Benutzers festgelegt. AuthenticationMethod wird bei der Authentifizierung des Benutzers vom Framework automatisch aufgefüllt.

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

Erstellen Sie eine Factory, mit der AccountClaimsPrincipalFactory<TAccount> erweitert wird, um aus den in CustomUserAccount.AuthenticationMethodgespeicherten Authentifizierungsmethoden des Benutzers Ansprüche zu erstellen:

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

Registrieren Sie die CustomAccountFactory für den verwendeten Authentifizierungsanbieter. Sie können die folgenden Registrierungen verwenden:

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

ME-ID-Sicherheitsgruppen und -Rollen mit einer benutzerdefinierten Benutzerkontoklasse

Ein weiteres Beispiel, das mit ME-ID-Sicherheitsgruppen und ME-ID-Administratorrollen sowie einer benutzerdefinierten Benutzerkontenklasse funktioniert, finden Sie unter ASP.NET Core Blazor WebAssembly mit Microsoft Entra ID-Gruppen und -Rollen.

Vorabrendering mit Authentifizierung

Das Vorabrendering von Inhalten, die eine Authentifizierung und Autorisierung erfordern, wird derzeit nicht unterstützt. Nachdem Sie die Anleitung in einem der Themen zu Blazor WebAssembly-Sicherheits-Apps befolgt haben, erstellen Sie anhand der folgenden Schritte eine App, die:

  • Pfade vorab rendert, für die keine Autorisierung erforderlich ist.
  • Keine Pfade vorab rendert, für die eine Autorisierung erforderlich ist.

Faktorieren Sie für das Projekt von Client die Program Datei allgemeine Dienstregistrierungen in einer separaten Methode (z. B. erstellen Sie eine ConfigureCommonServices -Methode im Client -Projekt). Allgemeine Dienste sind Dienste, die der Entwickler für die Verwendung durch Client- und Server-Projekte registriert.

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

In der Program-Datei:

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

builder.Services.AddScoped( ... );

ConfigureCommonServices(builder.Services);

await builder.Build().RunAsync();

Registrieren Sie im Server -Projekt die Program -Datei die folgenden zusätzlichen Dienste, und rufen Sie ConfigureCommonServices auf:

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

Registrieren Sie in der Startup.ConfigureServices-Methode des Server-Projekts die folgenden zusätzlichen Dienste, und rufen Sie ConfigureCommonServices auf:

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

Weitere Informationen zum Blazor-Frameworkserver-Authentifizierungsanbieter (ServerAuthenticationStateProvider) finden Sie unter Authentifizierung und Autorisierung in ASP.NET Core Blazor.

Ersetzen Sie in der Pages/_Host.cshtml-Datei des Server-Projekts das Taghilfsprogramm Component (<component ... />) durch Folgendes:

<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>

Im vorherigen Beispiel:

  • Der Platzhalter {CLIENT APP ASSEMBLY NAME} ist der Assemblyname der Client-App (z. B. BlazorSample.Client).
  • Die bedingte Überprüfung für das Pfadsegment /authentication:
    • Vermeidet das Prerendering (render-mode="WebAssembly") für Authentifizierungspfade.
    • Führt ein Prerendering (render-mode="WebAssemblyPrerendered") für Authentifizierungspfade aus.

Optionen für gehostete Apps und dritte Anmeldeanbieter

Wenn eine gehostete Blazor WebAssembly-App mit einem Drittanbieter authentifiziert und autorisiert wird, stehen für die Authentifizierung des Benutzers mehrere Optionen zur Verfügung. Welche Sie auswählen, hängt von Ihrem Szenario ab.

Weitere Informationen finden Sie unter Beibehalten zusätzlicher Ansprüche und Tokens von externen Anbieter*innen in ASP.NET Core.

Authentifizieren von Benutzern für das ausschließliche Aufrufen geschützter Drittanbieter-APIs

So authentifizieren Sie den Benutzer mit einem clientseitigen OAuth-Flow für den Drittanbieter einer API:

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

Szenario:

  • Der Server, der die App hostet, spielt keine Rolle.
  • APIs auf dem Server können nicht geschützt werden.
  • Die App kann nur geschützte APIs von Drittanbietern aufrufen.

Authentifizieren von Benutzern mit einem dritten Anbieter und Aufrufen geschützter APIs auf dem Hostserver und beim Drittanbieter

Konfigurieren Sie Identity mit einem dritten Anmeldeanbieter. Rufen Sie die Tokens ab, die für den Zugriff auf die API des Drittanbieters erforderlich sind, und speichern Sie sie.

Wenn sich ein Benutzer anmeldet, erfasst Identity als Teil des Authentifizierungsprozesses die Token für Zugriff und Aktualisierung. An dieser Stelle gibt es mehrere Ansätze, wie API-Aufrufe für APIs von Drittanbietern ausgeführt werden können.

Verwenden eines Serverzugriffstokens zum Abrufen des Zugriffstokens des Drittanbieters

Verwenden Sie das auf dem Server generierte Zugriffstoken, um das Zugriffstoken des Drittanbieters von einem API-Endpunkt des Servers abzurufen. Verwenden Sie dann das Zugriffstoken des Drittanbieters, um API-Ressourcen von Drittanbietern direkt über Identity auf dem Client abzurufen.

Wir empfehlen diesen Ansatz nicht. Dieser Ansatz erfordert die Behandlung des Zugriffstokens von Drittanbietern, als ob es für einen öffentlichen Client generiert wurde. In OAuth-Konzepten ausgedrückt bedeutet dies Folgendes: Die öffentliche App verfügt über keinen geheimen Clientschlüssel, da sie nicht als vertrauenswürdig genug gilt, um Geheimnisse sicher zu speichern. Das Zugriffstoken kann jedoch nur einem vertrauenswürdigen Client übergeben werden. Ein vertrauenswürdiger Client ist ein Client, der über einen geheimen Clientschlüssel verfügt, und es wird davon ausgegangen, dass er Geheimnisse sicher speichern kann.

  • Dem Drittanbieterzugriffstoken wird möglicherweise Zugriff auf zusätzliche Bereiche gewährt, um vertrauliche Vorgänge auszuführen. Das basiert auf der Tatsache, dass der Drittanbieter das Token für einen vertrauenswürdigeren Client übergeben hat.
  • Ähnlich verhält es sich mit Aktualisierungstokens, die nicht für einen Client übergeben werden sollten, der als nicht vertrauenswürdig gilt. Würden Sie dies tun, erhält der Client uneingeschränkten Zugriff, es sei denn, es gelten anderweitige Einschränkungen.

Durchführen von API-Aufrufen auf dem Client für die Server-API zum Aufrufen von Drittanbieter-APIs

Führen Sie einen API-Aufruf auf dem Client für die Server-API durch. Rufen Sie auf dem Server das Zugriffstoken für die API-Ressource des Drittanbieters ab, und führen Sie danach den Aufruf aus, der erforderlich ist.

Wir empfehlen diesen Ansatz. Obwohl für diesen Ansatz ein zusätzlicher Netzwerkhop auf dem Server erforderlich ist, um eine Drittanbieter-API aufzurufen, ist es letztendlich ein sichererer Ansatz:

  • Der Server kann Aktualisierungstokens speichern und dafür sorgen, dass die App nicht den Zugriff auf Drittanbieterressourcen verliert.
  • Die App kann Zugriffstokens auf dem Server nicht unberechtigterweise veröffentlichen, für den Berechtigungen mit noch höherer Vertraulichkeit erforderlich sind.

Verwenden von OpenID Connect 2.0-Endpunkten (OIDC)

Die Authentifizierungsbibliothek sowie Blazor-Projektvorlagen verwenden OpenID Connect 1.0-Endpunkte (OIDC). Wenn Sie einen Endpunkt der Version 2.0 verwenden möchten, konfigurieren Sie die JWT-Bearer-Option JwtBearerOptions.Authority. Im folgenden Beispiel wird ME-ID für Version 2.0 konfiguriert, indem ein v2.0-Segment an die Authority-Eigenschaft angehängt wird:

using Microsoft.AspNetCore.Authentication.JwtBearer;

...

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

Alternativ kann die Einstellung in der Datei mit den App-Einstellungen (appsettings.json) festgelegt werden:

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

Wenn das Anheften eines Segments an die Autorität für den OIDC-Anbieter der App nicht geeignet ist (z. B. bei Nicht-ME-ID-Anbietern), legen Sie die Authority-Eigenschaft direkt fest. Legen Sie die Eigenschaft entweder in JwtBearerOptions oder in der Datei mit den App-Einstellungen (appsettings.json) mit dem Authority-Schlüssel fest.

Die Liste der Ansprüche im ID-Token ändert sich für Endpunkte der Version 2.0. Die Microsoft-Dokumentation zu den Änderungen wurde eingestellt, aber Anleitungen zu den Ansprüchen in einem ID-Token sind in der Referenz für ID-Tokenansprüche verfügbar.

Konfigurieren und Verwenden von gRPC in Komponenten

So konfigurieren Sie eine Blazor WebAssembly-App zur Verwendung des ASP.NET Core gRPC-Frameworks:

Hinweis

Das Vorabrendering ist in Blazor-Web-Apps standardmäßig aktiviert. Daher müssen Sie zuerst das Rendern der Komponente durch den Server und dann durch den Client berücksichtigen. Jeder vorab gerenderte Zustand sollte an den Client übertragen werden, damit er wiederverwendet werden kann. Weitere Informationen finden Sie unter Prerendering von Razor-Komponenten in ASP.NET Core.

Hinweis

Das Vorabrendering ist in gehosteten Blazor WebAssembly-Apps standardmäßig aktiviert. Daher müssen Sie zuerst das Rendern der Komponente durch den Server und dann durch den Client berücksichtigen. Jeder vorab gerenderte Zustand sollte an den Client übertragen werden, damit er wiederverwendet werden kann. Weitere Informationen finden Sie unter Prerendering und Integrieren von Razor-Komponenten in ASP.NET Core.

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

Eine Komponente in der Client-App kann gRPC-Aufrufe mithilfe des gRPC-Clients (Grpc.razor) ausführen:

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

Verwenden Sie Version 2.30.0 oder höher von Grpc.Net.Client, um die Status.DebugException-Eigenschaft zu verwenden.

Weitere Informationen finden Sie unter gRPC-Web in ASP.NET Core gRPC-Apps.

Ersetzen der AuthenticationService-Implementierung

In den folgenden Unterabschnitten wird erläutert, wie Sie Folgendes ersetzen:

  • Jede JavaScript-AuthenticationService-Implementierung.
  • Die Microsoft Authentication Library für JavaScript (MSAL.js).

Ersetzen jeder JavaScript-AuthenticationService-Implementierung

Erstellen Sie eine JavaScript-Bibliothek, um Ihre benutzerdefinierten Authentifizierungsdetails zu behandeln.

Warnung

Bei dem Leitfaden in diesem Abschnitt handelt es sich um ein Implementierungsdetail des Standards von RemoteAuthenticationService<TRemoteAuthenticationState,TAccount,TProviderOptions>. Der TypeScript-Code in diesem Abschnitt gilt speziell für ASP.NET Core in .NET 7 und kann jederzeit ohne vorherige Ankündigung in neuen Releases von ASP.NET Core geändert werden.

// .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 };
};

Sie können die Bibliothek importieren, indem Sie das ursprüngliche <script>-Tag entfernen und ein <script>-Tag hinzufügen, das die benutzerdefinierte Bibliothek lädt. Im folgenden Beispiel wird veranschaulicht, wie das Standard-<script>-Tag durch ein Standardtag ersetzt wird, das eine Bibliothek mit dem Namen CustomAuthenticationService.js aus dem Ordner wwwroot/js lädt.

In wwwroot/index.html vor dem Skript Blazor (_framework/blazor.webassembly.js) innerhalb des schließenden </body>-Tags:

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

Weitere Informationen finden Sie unter AuthenticationService.ts im dotnet/aspnetcore-GitHub-Repository.

Hinweis

Dokumentationslinks zur .NET-Referenzquelle laden in der Regel den Standardbranch des Repositorys, der die aktuelle Entwicklung für das nächste Release von .NET darstellt. Um ein Tag für ein bestimmtes Release auszuwählen, wählen Sie diesen mit der Dropdownliste Switch branches or tags (Branches oder Tags wechseln) aus. Weitere Informationen finden Sie unter How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Auswählen eines Versionstags von ASP.NET Core-Quellcode (dotnet/AspNetCore.Docs #26205)).

Ersetzen der Microsoft Authentication Library für JavaScript (MSAL.js)

Wenn eine App eine benutzerdefinierte Version der Microsoft Authentication Library for JavaScript (MSAL.js) erfordert, führen Sie die folgenden Schritte aus:

  1. Vergewissern Sie sich, dass das neueste .NET SDK für Entwickler auf dem System vorhanden ist, oder rufen Sie das neueste Entwickler-SDK von der folgenden Seite ab, und installieren Sie es: .NET Core SDK: Installationsprogramme und Binärdateien. Eine Konfiguration der internen NuGet-Feeds ist für dieses Szenario nicht erforderlich.
  2. Richten Sie das dotnet/aspnetcore-GitHub-Repository für die Entwicklung gemäß der Dokumentation unter Erstellen von ASP.NET Core aus einer Quelle ein. Forken und klonen Sie ein ZIP-Archiv des dotnet/aspnetcore-GitHub-Repositorys oder laden Sie eines herunter.
  3. Öffnen Sie die Datei src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json, und legen Sie die gewünschte Version von @azure/msal-browser fest. Eine Liste der veröffentlichten Versionen finden Sie auf der npm-Website für @azure/msal-browser. Wählen Sie dort die Registerkarte Versionen aus.
  4. Kompilieren Sie das Projekt Authentication.Msal im Ordner src/Components/WebAssembly/Authentication.Msal/src. Verwenden Sie dafür den Befehl yarn build in einer Befehlsshell.
  5. Wenn die App komprimierte Ressourcen (Brotli/Gzip) verwendet, komprimieren Sie die Datei Interop/dist/Release/AuthenticationService.js.
  6. Kopieren Sie die Datei AuthenticationService.js und die komprimierten Versionen der Datei (.br/.gz), sofern diese erzeugt wurden, aus dem Ordner Interop/dist/Release in den App-Ordner publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal in den veröffentlichten Ressourcen der App.

Übergeben von benutzerdefinierten Anbieteroptionen

Definieren Sie eine Klasse zum Übergeben der Daten an die zugrunde liegende JavaScript-Bibliothek.

Wichtig

Die Struktur der Klasse muss mit dem übereinstimmen, was die Bibliothek erwartet, wenn JS-ON mit System.Text.Json serialisiert wird.

Im folgenden Beispiel wird eine ProviderOptions-Klasse mit JsonPropertyName-Attributen veranschaulicht, die den Erwartungen einer hypothetischen benutzerdefinierten Anbieterbibliothek entspricht:

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

Registrieren Sie die Anbieteroptionen im DI-System, und konfigurieren Sie die entsprechenden Werte:

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 = "...";
    });

Im vorherigen Beispiel werden Umleitungs-URIs mit regulären Zeichenfolgenliteralen festgelegt. Die folgenden Alternativen sind verfügbar:

  • TryCreate mit Verwendung von IWebAssemblyHostEnvironment.BaseAddress:

    Uri.TryCreate(
        $"{builder.HostEnvironment.BaseAddress}authentication/login-callback", 
        UriKind.Absolute, out var redirectUri);
    options.RedirectUri = redirectUri;
    
  • Konfiguration des Host-Generators:

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

    wwwroot/appsettings.json:

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

Zusätzliche Ressourcen