Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 10-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 10-Version dieses Artikels.
In diesem Artikel wird erläutert, wie Sie Blazor serverseitig für zusätzliche Sicherheitsszenarios konfigurieren, einschließlich der Tokenübergabe an eine Blazor-App.
Hinweis
Die Codebeispiele in diesem Artikel verwenden Nullwerte zulassende Verweistypen (Nullable Reference Types, NRTs) und die statische Analyse des NULL-Zustands des .NET-Compilers, die in ASP.NET Core in .NET 6 oder höher unterstützt werden. Entfernen Sie beim Targeting von .NET 5 oder einer früheren Version die Nulltypbezeichnung (?) aus den string?, TodoItem[]?, WeatherForecast[]? und IEnumerable<GitHubBranch>?-Typen in den Beispielen des Artikels.
Token an eine serverseitige Blazor-App übergeben
Dieser Abschnitt gilt für Blazor Web Apps. Blazor ServerSehen Sie sich die .NET 7-Version dieses Artikels an.
Wenn Sie lediglich Zugriffstoken verwenden möchten, um Web-API-Aufrufe von einem Blazor Web Appmit einem benannten HTTP-Client auszuführen, lesen Sie den Abschnitt "Verwenden eines Tokenhandlers für Web-API-Aufrufe ", in dem erläutert wird, wie Sie mithilfe einer DelegatingHandler Implementierung das Zugriffstoken eines Benutzers an ausgehende Anforderungen anfügen. Die folgende Anleitung in diesem Abschnitt richtet sich an Entwickler, die Zugriffstoken, Aktualisierungstoken und andere serverseitige Authentifizierungseigenschaften benötigen.
Hinweis
Weitere Informationen zu DelegatingHandler-Instanzen finden Sie unter HTTP-Anforderungen mit IHttpClientFactory – ASP.NET Core.
Um Token und andere Authentifizierungseigenschaften für die serverseitige Verwendung in Blazor Web Apps zu speichern, empfehlen wir die Verwendung IHttpContextAccessor/HttpContext (IHttpContextAccessor, HttpContext). Das Lesen von Token aus HttpContext, einschließlich als kaskadierender Parameter mithilfe von IHttpContextAccessor wird unterstützt, um Token für die Verwendung während des interaktiven Serverrenderings zu erhalten, wenn die Token während des statischen serverseitigen Renderings (statisches SSR) oder des Vorrenderings abgerufen werden. Token werden jedoch nicht aktualisiert, wenn sich der Benutzer authentifiziert, nachdem der Schaltkreis eingerichtet wurde, da das HttpContext Token zu Beginn der SignalR Verbindung erfasst wird. Außerdem bedeutet die Verwendung von AsyncLocal<T> durch IHttpContextAccessor, dass Sie darauf achten müssen, den Ausführungskontext nicht zu verlieren, bevor Sie HttpContext lesen. Weitere Informationen finden Sie unter IHttpContextAccessor/HttpContext in ASP.NET Core Blazor Apps.
Rufen Sie in einer Dienstklasse Zugriff auf die Mitglieder des Namespaces Microsoft.AspNetCore.Authentication ab, um die GetTokenAsync-Methode auf HttpContext bereitzustellen. Ein alternativer Ansatz, der im folgenden Beispiel auskommentiert ist, besteht darin, AuthenticateAsync auf HttpContext aufzurufen. Rufen Sie für den zurückgegebenen AuthenticateResult.PropertiesGetTokenValue auf.
using Microsoft.AspNetCore.Authentication;
public class AuthenticationProcessor(IHttpContextAccessor httpContextAccessor)
{
public async Task<string?> GetAccessToken()
{
if (httpContextAccessor.HttpContext is null)
{
throw new Exception("HttpContext not available");
}
// Approach 1: Call 'GetTokenAsync'
var accessToken = await httpContextAccessor.HttpContext
.GetTokenAsync("access_token");
// Approach 2: Authenticate the user and call 'GetTokenValue'
/*
var authResult = await httpContextAccessor.HttpContext.AuthenticateAsync();
var accessToken = authResult?.Properties?.GetTokenValue("access_token");
*/
return accessToken;
}
}
Der Dienst wird in der Datei des Serverprojekts Program registriert:
builder.Services.AddScoped<AuthenticationProcessor>();
AuthenticationProcessor kann in serverseitige Dienste eingefügt werden, z. B. in einer DelegatingHandler für eine vorkonfigurierte HttpClient. Das folgende Beispiel dient nur zu Demonstrationszwecken oder für den Fall, dass Sie eine spezielle Verarbeitung im AuthenticationProcessor Dienst ausführen müssen, da Sie das Token einfach direkt zum Aufrufen externer Web-APIs einfügen IHttpContextAccessor und abrufen können (weitere Informationen zur direkten Verwendung IHttpContextAccessor von Web-APIs finden Sie im Abschnitt "Verwenden eines Tokenhandlers für Web-API-Aufrufe ").
using System.Net.Http.Headers;
public class TokenHandler(AuthenticationProcessor authProcessor) :
DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var accessToken = authProcessor.GetAccessToken();
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
return await base.SendAsync(request, cancellationToken);
}
}
Der Tokenhandler wird registriert und fungiert als delegierender Handler für einen benannten HTTP-Client in der Program Datei:
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<TokenHandler>();
builder.Services.AddHttpClient("ExternalApi",
client => client.BaseAddress = new Uri(builder.Configuration["ExternalApiUri"] ??
throw new Exception("Missing base address!")))
.AddHttpMessageHandler<TokenHandler>();
Vorsicht
Stellen Sie sicher, dass Token niemals vom Client (dem .Client Projekt) übertragen und verarbeitet werden, z. B. in einer Komponente, die interaktives Autorendering verwendet und auf dem Client oder von einem clientseitigen Dienst gerendert wird. Sorgen Sie dafür, dass der Client immer den Server (Projekt) aufruft, um Anforderungen mit Authentifizierungstoken zu verarbeiten.
Token und andere Authentifizierungsdaten sollten niemals den Server verlassen.
Interaktive Auto-Komponenten finden Sie unter ASP.NET Core-Authentifizierung Blazor und -Autorisierung, die zeigt, wie Zugriffstoken und andere Authentifizierungseigenschaften auf dem Server beibehalten werden. Erwägen Sie außerdem die Übernahme des Backend-for-Frontend (BFF)-Musters, das eine ähnliche Aufrufstruktur übernimmt und im Abschnitt Blazor Web App für OIDC-Anbieter sowie in ASP.NET Core mit Microsoft Entra ID absichern für Microsoft Blazor Web App Web mit Entra beschrieben wird.
Verwenden eines Tokenhandlers für Web-API-Aufrufe
Der folgende Ansatz zielt darauf ab, das Zugriffstoken eines Benutzers an ausgehende Anforderungen anzufügen, insbesondere um Web-API-Aufrufe an externe Web-API-Apps zu senden. Der Ansatz wird für eine Blazor Web App gezeigt, die globales interaktives Server-Rendering nutzt, der gleiche allgemeine Ansatz gilt aber für Blazor Web Apps, die den globalen interaktiven automatischen Rendermodus übernehmen. Das wichtige Konzept, das zu beachten ist, besteht darin, dass der Zugriff auf die HttpContext mithilfe von IHttpContextAccessor nur auf dem Server ausgeführt wird.
Für eine Demonstration der Anleitung in diesem Abschnitt, sehen Sie sich die BlazorWebAppOidcBlazorWebAppOidcServer Beispielanwendungen (.NET 8 oder höher) im Blazor Beispiel-GitHub-Repository an. Die Beispiele übernehmen einen globalen interaktiven Rendermodus und die OIDC-Authentifizierung mit Microsoft Entra, ohne entra-spezifische Pakete zu verwenden. In den Beispielen wird veranschaulicht, wie ein JWT-Zugriffstoken übergeben wird, um eine sichere Web-API aufzurufen.
Microsoft Identity Platform mit Microsoft Identity Web-Pakete für Microsoft Entra ID bietet eine API zum Aufrufen von Web-APIs von Blazor Web Apps mit automatischer Tokenverwaltung und -erneuerung. Weitere Informationen finden Sie unter Sichern einer ASP.NET Core-Blazor Web App mit Microsoft Entra ID und der BlazorWebAppEntra und BlazorWebAppEntraBff Beispiel-Apps (.NET 9 oder später) im Blazor GitHub-Beispielrepository.
Verwenden Sie die Subklasse DelegatingHandler, um das Zugriffstoken eines Benutzers an ausgehende Anfragen anzuhängen. Der Tokenhandler wird nur auf dem Server ausgeführt, daher ist die Verwendung HttpContext sicher.
TokenHandler.cs:
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Authentication;
public class TokenHandler(IHttpContextAccessor httpContextAccessor) :
DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (httpContextAccessor.HttpContext is null)
{
throw new Exception("HttpContext not available");
}
var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
if (accessToken is null)
{
throw new Exception("No access token");
}
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
return await base.SendAsync(request, cancellationToken);
}
}
Hinweis
Anleitungen dazu, wie Sie auf einen AuthenticationStateProvider von einem DelegatingHandler zugreifen, finden Sie im Abschnitt ZugriffAuthenticationStateProvider in Middleware für ausgehende Anforderungen.
In der Datei des Program Projekts wird der Tokenhandler (TokenHandler) als bereichsbezogener Dienst registriert und als Nachrichtenhandler des benannten HTTP-Clients mit AddHttpMessageHandlerangegeben.
Im folgenden Beispiel ist der {HTTP CLIENT NAME} Platzhalter der Name des HttpClient, und der {BASE ADDRESS} Platzhalter ist der Basisadressen-URI der Web-API. Weitere Informationen zu AddHttpContextAccessor finden Sie unter IHttpContextAccessor/HttpContext in ASP.NET Core-AnwendungenBlazor.
In Program.cs:
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<TokenHandler>();
builder.Services.AddHttpClient("{HTTP CLIENT NAME}",
client => client.BaseAddress = new Uri("{BASE ADDRESS}"))
.AddHttpMessageHandler<TokenHandler>();
Beispiel:
builder.Services.AddScoped<TokenHandler>();
builder.Services.AddHttpClient("ExternalApi",
client => client.BaseAddress = new Uri("https://localhost:7277"))
.AddHttpMessageHandler<TokenHandler>();
Sie können die HTTP-Clientbasisadresse aus der Konfiguration mit builder.Configuration["{CONFIGURATION KEY}"] angeben, wobei der Platzhalter der {CONFIGURATION KEY}Konfigurationsschlüssel ist.
new Uri(builder.Configuration["ExternalApiUri"] ?? throw new IOException("No URI!"))
Geben Sie in appsettings.json das ExternalApiUri an. Im folgenden Beispiel wird der Wert auf die localhost-Adresse der externen Web-API auf https://localhost:7277 gesetzt.
"ExternalApiUri": "https://localhost:7277"
Zu diesem Zeitpunkt kann eine HttpClient von einer Komponente erstellte Komponente sichere Web-API-Anforderungen erstellen. Im folgenden Beispiel ist {REQUEST URI} der zugehörige Anforderungs-URI, und der {HTTP CLIENT NAME} Platzhalter ist der Name des HttpClient:
using var request = new HttpRequestMessage(HttpMethod.Get, "{REQUEST URI}");
var client = ClientFactory.CreateClient("{HTTP CLIENT NAME}");
using var response = await client.SendAsync(request);
Beispiel:
using var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast");
var client = ClientFactory.CreateClient("ExternalApi");
using var response = await client.SendAsync(request);
Zusätzliche Funktionen sind für Blazor geplant, die von Access AuthenticationStateProvider in der Middleware für ausgehende Anforderungen (dotnet/aspnetcore #52379) nachverfolgt werden.
Problem beim Bereitstellen von Zugriffstoken für HttpClient im interaktiven Servermodus (dotnet/aspnetcore #52390) ist ein geschlossenes Problem, das hilfreiche Diskussions- und potenzielle Problemumgehungsstrategien für erweiterte Anwendungsfälle enthält.
Token, die außerhalb der Razor-Komponenten in einer serverseitigen Blazor-App verfügbar sind, können mit dem in diesem Abschnitt beschriebenen Ansatz an Komponenten übergeben werden. Das Beispiel in diesem Abschnitt konzentriert sich auf die Übergabe, von Zugriffstoken, Aktualisierungstoken und Anforderungsfälschungssicherheits-Token (XSRF) an die Blazor-App, aber der Ansatz ist auch für andere HTTP-Kontextzustände gültig.
Hinweis
Das Übergeben des XSRF-Tokens an Razor-Komponenten ist in Szenarien nützlich, in denen Komponenten an Identity oder andere Endpunkte posten, die eine Überprüfung erfordern. Wenn Ihre App nur Zugriffs- und Aktualisierungstoken erfordert, können Sie den XSRF-Tokencode aus dem folgenden Beispiel entfernen.
Authentifizieren Sie die App genauso wie eine reguläre Razor Pages- oder MVC-App. Stellen Sie die Token bereit und speichern Sie diese für die Authentifizierungcookie.
In der Program-Datei:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
...
builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});
In Startup.cs:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
...
services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});
In Startup.cs:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
...
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});
Optional werden zusätzliche Bereiche mit options.Scope.Add("{SCOPE}"); hinzugefügt, wobei der Platzhalter {SCOPE} der zusätzliche hinzuzufügende Bereich ist.
Definieren Sie einen bereichsbezogenen Tokenanbieterdienst, der innerhalb der Blazor-App verwendet werden kann, um die Token aus der Abhängigkeitsinjektion (Dependency Injection, DI) aufzulösen.
TokenProvider.cs:
public class TokenProvider
{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
public string? XsrfToken { get; set; }
}
Fügen Sie in der Program-Datei Dienste für Folgendes hinzu:
-
IHttpClientFactory: Wird in einer
WeatherForecastService-Klasse verwendet, die Wetterdaten von einer Server-API mit einem Zugriffstoken abruft. -
TokenProvider: Enthält die Zugriffs- und Aktualisierungstoken.
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();
Fügen Sie in Startup.ConfigureServices von Startup.cs Dienste für Folgendes hinzu:
-
IHttpClientFactory: Wird in einer
WeatherForecastService-Klasse verwendet, die Wetterdaten von einer Server-API mit einem Zugriffstoken abruft. -
TokenProvider: Enthält die Zugriffs- und Aktualisierungstoken.
services.AddHttpClient();
services.AddScoped<TokenProvider>();
Definieren Sie eine Klasse, um den anfänglichen App-Zustand mit den Zugriffs- und Aktualisierungstoken zu übergeben.
InitialApplicationState.cs:
public class InitialApplicationState
{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
public string? XsrfToken { get; set; }
}
Erstellen Sie in der Datei Pages/_Host.cshtml eine Instanz von InitialApplicationState, und übergeben Sie diese als Parameter an die App:
Erstellen Sie in der Datei Pages/_Layout.cshtml eine Instanz von InitialApplicationState, und übergeben Sie diese als Parameter an die App:
Erstellen Sie in der Datei Pages/_Host.cshtml eine Instanz von InitialApplicationState, und übergeben Sie diese als Parameter an die App:
@using Microsoft.AspNetCore.Authentication
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
...
@{
var tokens = new InitialApplicationState
{
AccessToken = await HttpContext.GetTokenAsync("access_token"),
RefreshToken = await HttpContext.GetTokenAsync("refresh_token"),
XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken
};
}
<component ... param-InitialState="tokens" ... />
Lösen Sie in der App-Komponente (App.razor) den Dienst auf, und initialisieren Sie diesen mit den Daten aus dem Parameter:
@inject TokenProvider TokenProvider
...
@code {
[Parameter]
public InitialApplicationState? InitialState { get; set; }
protected override Task OnInitializedAsync()
{
TokenProvider.AccessToken = InitialState?.AccessToken;
TokenProvider.RefreshToken = InitialState?.RefreshToken;
TokenProvider.XsrfToken = InitialState?.XsrfToken;
return base.OnInitializedAsync();
}
}
Hinweis
Eine Alternative zum Zuweisen des Anfangszustands zum TokenProvider im vorherigen Beispiel besteht darin, die Daten in einen bereichsbezogenen Dienst innerhalb von OnInitializedAsync für Verwendung in der App zu kopieren.
Fügen Sie der App einen Paketverweis für das NuGet-Paket Microsoft.AspNet.WebApi.Client hinzu.
Hinweis
Eine Anleitung zum Hinzufügen von Paketen zu .NET-Anwendungen finden Sie in den Artikeln unter Pakete installieren und verwalten unter Workflow für die Paketnutzung (NuGet-Dokumentation). Überprüfen Sie unter NuGet.org, ob die richtige Paketversion verwendet wird.
Fügen Sie den Tokenanbieter in den Dienst ein, der eine sichere API-Anforderung stellt, und rufen Sie das Token für die API-Anforderung ab:
WeatherForecastService.cs:
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class WeatherForecastService
{
private readonly HttpClient http;
private readonly TokenProvider tokenProvider;
public WeatherForecastService(IHttpClientFactory clientFactory,
TokenProvider tokenProvider)
{
http = clientFactory.CreateClient();
this.tokenProvider = tokenProvider;
}
public async Task<WeatherForecast[]> GetForecastAsync()
{
var token = tokenProvider.AccessToken;
using var request = new HttpRequestMessage(HttpMethod.Get,
"https://localhost:5003/WeatherForecast");
request.Headers.Add("Authorization", $"Bearer {token}");
using var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ??
Array.Empty<WeatherForecast>();
}
}
Fügen Sie für ein XSRF-Token, das an eine Komponente übergeben wird, den TokenProvider ein, und fügen Sie das XSRF-Token der POST-Anforderung hinzu. Im folgenden Beispiel wird das Token einem Abmeldeendpunkt- POST hinzugefügt. Das Szenario für das folgende Beispiel ist, dass der Abmeldeendpunkt (Areas/Identity/Pages/Account/Logout.cshtml, in die App eingefügt) keinen IgnoreAntiforgeryTokenAttribute (@attribute [IgnoreAntiforgeryToken]) angibt, da er zusätzlich zu einem normalen Abmeldevorgang, der geschützt werden muss, eine Aktion ausführt. Der Endpunkt erfordert ein gültiges XSRF-Token, um die Anforderung erfolgreich verarbeiten zu können.
In einer Komponente, die autorisierten Benutzern eine Abmeldeschaltfläche bietet:
@inject TokenProvider TokenProvider
...
<AuthorizeView>
<Authorized>
<form action="/Identity/Account/Logout?returnUrl=%2F" method="post">
<button class="nav-link btn btn-link" type="submit">Logout</button>
<input name="__RequestVerificationToken" type="hidden"
value="@TokenProvider.XsrfToken">
</form>
</Authorized>
<NotAuthorized>
...
</NotAuthorized>
</AuthorizeView>
Festlegen des Authentifizierungsschemas
Für eine App, die mehr als eine Authentifizierungsmiddleware verwendet und daher über mehr als ein Authentifizierungsschema verfügt, kann das von Blazor verwendete Schema explizit in der Endpunktkonfiguration der Program-Datei festgelegt werden. Im folgenden Beispiel wird das OpenID Connect-Schema (OIDC) festgelegt:
Für eine App, die mehr als eine Authentifizierungsmiddleware verwendet und daher über mehr als ein Authentifizierungsschema verfügt, kann das von Blazor verwendete Schema explizit in der Endpunktkonfiguration von Startup.cs festgelegt werden. Im folgenden Beispiel wird das OpenID Connect-Schema (OIDC) festgelegt:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
...
app.MapRazorComponents<App>().RequireAuthorization(
new AuthorizeAttribute
{
AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
})
.AddInteractiveServerRenderMode();
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
...
app.MapBlazorHub().RequireAuthorization(
new AuthorizeAttribute
{
AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
});
Für eine App, die mehr als eine Authentifizierungsmiddleware verwendet und daher über mehr als ein Authentifizierungsschema verfügt, kann das von Blazor verwendete Schema explizit in der Endpunktkonfiguration von Startup.Configure festgelegt werden. Im folgenden Beispiel wird das Microsoft Entra ID-Schema festgelegt:
endpoints.MapBlazorHub().RequireAuthorization(
new AuthorizeAttribute
{
AuthenticationSchemes = AzureADDefaults.AuthenticationScheme
});
Verwenden von OpenID Connect v2.0-Endpunkten (OIDC)
In Versionen von ASP.NET Core vor .NET 5 verwenden die Authentifizierungsbibliothek und Blazor -vorlagen OpenID Connect (OIDC) v1.0-Endpunkte. Um einen Endpunkt v2.0 mit Versionen von ASP.NET Core vor .NET 5 zu verwenden, konfigurieren Sie die Option `OpenIdConnectOptions.Authority` unter `OpenIdConnectOptions`.
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme,
options =>
{
options.Authority += "/v2.0";
}
Alternativ kann die Einstellung in der Datei mit den App-Einstellungen (appsettings.json) festgelegt werden:
{
"AzureAd": {
"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 OpenIdConnectOptions oder in der Datei mit den App-Einstellungen mit dem Authority-Schlüssel fest.
Codeänderungen
Die Liste der Ansprüche im ID-Token ändert sich für Endpunkte der Version 2.0. Microsoft-Dokumentation zu den Änderungen wurde eingestellt, aber Anleitungen zu den Ansprüchen in einem ID-Token sind im ID-Token-Anspruchsverweis verfügbar.
Da Ressourcen in Scope-URIs für v2.0-Endpunkte angegeben werden, entfernen Sie die OpenIdConnectOptions.Resource-Eigenschaftseinstellung in OpenIdConnectOptions.
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => { ... options.Resource = "..."; // REMOVE THIS LINE ... }
App-ID-URI
- Bei der Verwendung von v2.0-Endpunkten definieren APIs einen
App ID URI, der einen eindeutigen Bezeichner für die API darstellen soll. - Alle Bereiche enthalten den App-ID-URI als Präfix, und die v2.0-Endpunkte geben Zugriffstoken mit dem App-ID-URI als Zielgruppe aus.
- Bei Verwendung von v2.0-Endpunkten ändert sich die in der Server-API konfigurierte Client-ID von der API-Anwendungs-ID (Client-ID) in den App-ID-URI.
appsettings.json:
{
"AzureAd": {
...
"ClientId": "https://{TENANT}.onmicrosoft.com/{PROJECT NAME}"
...
}
}
Sie finden den zu verwendenden App-ID-URI in der Beschreibung zur App-Registrierung des OIDC-Anbieters.
Schaltungshandler zum Erfassen von Benutzern für benutzerdefinierte Dienste
Verwenden Sie einen CircuitHandler, um einen Benutzer aus dem AuthenticationStateProvider zu erfassen und den Benutzer in einem Dienst festzulegen. Wenn Sie eine*n Benutzer*in aktualisieren möchten, registrieren Sie einen Rückruf an AuthenticationStateChanged, und stellen Sie einen Task in die Warteschlange, um den/die Benutzer*in abzurufen und den Dienst zu aktualisieren. Im folgenden Beispiel wird dieser Ansatz veranschaulicht.
Im folgenden Beispiel:
-
OnConnectionUpAsync wird jedes Mal aufgerufen, wenn die Verbindung wiederhergestellt wird, wobei der Benutzer für die Lebensdauer der Verbindung festgelegt wird. Nur die OnConnectionUpAsync-Methode ist erforderlich, es sei denn, Sie implementieren Updates über einen Handler für Authentifizierungsänderungen (
AuthenticationChangedim folgenden Beispiel). -
OnCircuitOpenedAsync wird aufgerufen, um den Handler für geänderte Authentifizierung (
AuthenticationChanged) anzufügen, um den Benutzer zu aktualisieren. - Der
catch-Block derUpdateAuthentication-Aufgabe reagiert nicht auf Ausnahmen, da es zu diesem Zeitpunkt der Codeausführung keine Möglichkeit gibt, die Ausnahmen zu melden. Wenn eine Ausnahme vom Task ausgelöst wird, wird die Ausnahme an anderer Stelle in der App gemeldet.
UserService.cs:
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;
public class UserService
{
private ClaimsPrincipal currentUser = new(new ClaimsIdentity());
public ClaimsPrincipal GetUser() => currentUser;
internal void SetUser(ClaimsPrincipal user)
{
if (currentUser != user)
{
currentUser = user;
}
}
}
internal sealed class UserCircuitHandler(
AuthenticationStateProvider authenticationStateProvider,
UserService userService)
: CircuitHandler, IDisposable
{
public override Task OnCircuitOpenedAsync(Circuit circuit,
CancellationToken cancellationToken)
{
authenticationStateProvider.AuthenticationStateChanged +=
AuthenticationChanged;
return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}
private void AuthenticationChanged(Task<AuthenticationState> task)
{
_ = UpdateAuthentication(task);
async Task UpdateAuthentication(Task<AuthenticationState> task)
{
try
{
var state = await task;
userService.SetUser(state.User);
}
catch
{
}
}
}
public override async Task OnConnectionUpAsync(Circuit circuit,
CancellationToken cancellationToken)
{
var state = await authenticationStateProvider.GetAuthenticationStateAsync();
userService.SetUser(state.User);
}
public void Dispose()
{
authenticationStateProvider.AuthenticationStateChanged -=
AuthenticationChanged;
}
}
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;
public class UserService
{
private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());
public ClaimsPrincipal GetUser()
{
return currentUser;
}
internal void SetUser(ClaimsPrincipal user)
{
if (currentUser != user)
{
currentUser = user;
}
}
}
internal sealed class UserCircuitHandler : CircuitHandler, IDisposable
{
private readonly AuthenticationStateProvider authenticationStateProvider;
private readonly UserService userService;
public UserCircuitHandler(
AuthenticationStateProvider authenticationStateProvider,
UserService userService)
{
this.authenticationStateProvider = authenticationStateProvider;
this.userService = userService;
}
public override Task OnCircuitOpenedAsync(Circuit circuit,
CancellationToken cancellationToken)
{
authenticationStateProvider.AuthenticationStateChanged +=
AuthenticationChanged;
return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}
private void AuthenticationChanged(Task<AuthenticationState> task)
{
_ = UpdateAuthentication(task);
async Task UpdateAuthentication(Task<AuthenticationState> task)
{
try
{
var state = await task;
userService.SetUser(state.User);
}
catch
{
}
}
}
public override async Task OnConnectionUpAsync(Circuit circuit,
CancellationToken cancellationToken)
{
var state = await authenticationStateProvider.GetAuthenticationStateAsync();
userService.SetUser(state.User);
}
public void Dispose()
{
authenticationStateProvider.AuthenticationStateChanged -=
AuthenticationChanged;
}
}
In der Program-Datei:
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;
...
builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());
In Startup.ConfigureServices von Startup.cs:
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;
...
services.AddScoped<UserService>();
services.TryAddEnumerable(
ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());
Verwenden Sie den Dienst in einer Komponente, um den Benutzer abzurufen:
@inject UserService UserService
<h1>Hello, @(UserService.GetUser().Identity?.Name ?? "world")!</h1>
Um Benutzer*innen in Middleware für MVC, Razor Pages und in anderen ASP.NET Core-Szenarien festzulegen, rufen Sie SetUser für UserService in benutzerdefinierter Middleware auf, wenn die Authentifizierungsmiddleware ausgeführt wird, oder legen Sie Benutzer*innen mit einer IClaimsTransformation-Implementierung fest. Im folgenden Beispiel wird der Middlewareansatz übernommen.
UserServiceMiddleware.cs:
public class UserServiceMiddleware
{
private readonly RequestDelegate next;
public UserServiceMiddleware(RequestDelegate next)
{
this.next = next ?? throw new ArgumentNullException(nameof(next));
}
public async Task InvokeAsync(HttpContext context, UserService service)
{
service.SetUser(context.User);
await next(context);
}
}
Rufen Sie unmittelbar vor dem Aufruf von app.MapRazorComponents<App>() in der Program-Datei die Middleware auf:
Rufen Sie unmittelbar vor dem Aufruf von app.MapBlazorHub() in der Program-Datei die Middleware auf:
Rufen Sie unmittelbar vor dem Aufruf von app.MapBlazorHub() in Startup.Configure von Startup.cs die Middleware auf:
app.UseMiddleware<UserServiceMiddleware>();
Zugreifen auf AuthenticationStateProvider in Middleware für ausgehende Anforderungen
IHttpClientFactory erstellt DelegatingHandler Instanzen in einem von der App getrennten Bereich für Dependency Injection (DI). Wenn Sie AuthenticationStateProvider in einen abgeleiteten DelegatingHandler-Typ injizieren, hat der Handler keinen Zugriff auf den Authentifizierungsstatus des/der aktuellen Benutzer*in aus der Blazor-Verbindung.
Verwenden Sie einen der folgenden Ansätze, um dieses Szenario zu beheben:
Hinweis
Allgemeine Anleitungen zum Definieren der Delegierung von Handlern für HTTP-Anforderungen durch HttpClient Instanzen, die mit IHttpClientFactory erstellt wurden, finden Sie in den folgenden Abschnitten von HTTP-Anforderungen mit IHttpClientFactory – ASP.NET Core:
Die Beispiele in den folgenden Unterabschnitten fügen einen benutzerdefinierten Benutzernamenheader für authentifizierte Benutzer an ausgehende Anforderungen an.
Anwendungsbereichshandler (empfohlen)
Der Ansatz in diesem Abschnitt verwendet einen Verschlüsselter Dienst, um einen benutzerdefinierten HttpClient zu registrieren, der den Basisclient mit einem im aktuellen Anwendungsbereich aufgelösten Application-Scope-Handler umschließt, um Zugang zu AuthenticationStateProvider zu erhalten.
Übersicht über den Ansatz:
- Basiskonfiguration des Clients: AddHttpClient wird aufgerufen, um einen benannten Client bei IHttpClientFactory zu registrieren.
- Verschlüsselte Registrierung: Eine benutzerdefinierte
AddApplicationScopeHandler-Erweiterungsmethode registriert einen verschlüsselten HttpClient mit demselben Clientnamen. - Bereichsbewusster Handler: Der Anwendungsscope-Handler wird aus dem aktuellen Bereich heraus aufgelöst, was ihm Zugriff auf AuthenticationStateProvider gewährt.
- Zwischenspeicherung von Handlern: Der Anwendungsbereichshandler verwendet IHttpMessageHandlerFactory, um den zwischengespeicherten HttpMessageHandler abzurufen, der die Verbindungspoolung bewahrt.
- Wiederverwendung der Konfiguration: Der Anwendungsbereichshandler wendet dieselbe HttpClientFactoryOptions-Konfiguration seinen HttpClient als Basisclient an.
Erstellen Sie die folgenden Methoden und Klassen:
-
AddApplicationScopeHandler: Eine Erweiterungsmethode zum Hinzufügen eines Anwendungsbereich-Handlers und eines benannten Dienstes HttpClient zum DI-Container. -
ApplicationScopeHandler: Die Anwendungsbereichshandlerklasse. -
AuthenticationStateHandler: Ein DelegatingHandler, der einen benutzerdefinierten Benutzernamen-Header für authentifizierte Benutzende an ausgehende Anfragen anfügt.
Services/ApplicationScopeHttpClientExtensions.cs:
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Options;
namespace BlazorSample.Services;
public static class ApplicationScopeHttpClientExtensions
{
public static readonly HttpRequestOptionsKey<IServiceProvider> ScopeKey =
new("ApplicationScope");
public static IHttpClientBuilder AddApplicationScopeHandler(
this IHttpClientBuilder builder)
{
var name = builder.Name;
builder.Services.AddTransient<ApplicationScopeHandler>();
builder.Services.AddKeyedScoped<HttpClient>(name, (sp, key) =>
{
var handler = sp.GetRequiredService<ApplicationScopeHandler>();
handler.InnerHandler =
sp.GetRequiredService<IHttpMessageHandlerFactory>()
.CreateHandler(name);
var client = new HttpClient(handler, disposeHandler: false);
var options =
sp.GetRequiredService<IOptionsMonitor<HttpClientFactoryOptions>>()
.Get(name);
foreach (var action in options.HttpClientActions)
{
action(client);
}
return client;
});
return builder;
}
}
public class ApplicationScopeHandler(IServiceProvider serviceProvider)
: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
request.Options.Set(ApplicationScopeHttpClientExtensions.ScopeKey,
serviceProvider);
return base.SendAsync(request, cancellationToken);
}
}
public class AuthenticationStateHandler : DelegatingHandler
{
private ClaimsPrincipal? user;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (user is null)
{
if (request.Options.TryGetValue(
ApplicationScopeHttpClientExtensions.ScopeKey, out var sp))
{
var authStateProvider = sp.GetService<AuthenticationStateProvider>();
if (authStateProvider is not null)
{
user = (await authStateProvider.GetAuthenticationStateAsync())
.User;
}
}
}
if (user?.Identity?.IsAuthenticated)
{
request.Headers.TryAddWithoutValidation("X-USER-IDENTITY-NAME",
user.Identity.Name);
}
return await base.SendAsync(request, cancellationToken);
}
}
Im vorherigen AuthenticationStateHandler-Beispiel wird der Benutzer für die Lebensdauer des DelegatingHandler zwischengespeichert. Um den aktuellen Authentifizierungsstatus des/der Benutzer*in für jede Anforderung abzurufen, entfernen Sie die null bedingte Überprüfung für den/die Benutzer*in.
Registrieren Sie den benannten Client in der Program Datei, und rufen Sie auf AddApplicationScopeHandler , um den Anwendungsbereichshandler hinzuzufügen:
builder.Services.AddHttpClient("ExternalApi", client =>
{
client.BaseAddress = new Uri("{REQUEST URI}");
})
.AddApplicationScopeHandler()
.AddHttpMessageHandler<AuthenticationStateHandler>();
Der {REQUEST URI} Platzhalter im vorherigen Beispiel ist der Anforderungs-URI (localhost-Beispiel: http://localhost:5209).
Fügen Sie den Client mithilfe des verschlüsselten Dienst in Komponenten ein:
@using Microsoft.Extensions.DependencyInjection
@code {
[Inject(Key = "ExternalApi")]
public HttpClient Http { get; set; } = default!;
private async Task CallApiAsync()
{
var response = await Http.GetAsync("/api/endpoint");
}
}
Schaltkreisaktivitätshandler
Der Ansatz in diesem Abschnitt verwendet einen Schaltkreisaktivitätshandler, um auf den AuthenticationStateProvider zuzugreifen. Dies ist eine Alternative zum empfohlenen Anwendungsbereichshandleransatz im vorherigen Abschnitt.
Implementieren Sie zunächst die CircuitServicesAccessor-Klasse im folgenden Abschnitt des Artikels zur Blazor-Abhängigkeitsinjektion (Dependency Injection, DI):
Zugreifen auf serverseitige Blazor-Dienste aus einem anderen DI-Bereich
Verwenden Sie CircuitServicesAccessor, um auf AuthenticationStateProvider in der DelegatingHandler-Implementierung zuzugreifen.
AuthenticationStateHandler.cs:
using Microsoft.AspNetCore.Components.Authorization;
public class AuthenticationStateHandler(
CircuitServicesAccessor circuitServicesAccessor)
: DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var authStateProvider = circuitServicesAccessor.Services?
.GetRequiredService<AuthenticationStateProvider>();
if (authStateProvider is null)
{
throw new Exception("AuthenticationStateProvider not available");
}
var authState = await authStateProvider.GetAuthenticationStateAsync();
var user = authState?.User;
if (user?.Identity is not null && user.Identity.IsAuthenticated)
{
request.Headers.Add("X-USER-IDENTITY-NAME", user.Identity.Name);
}
return await base.SendAsync(request, cancellationToken);
}
}
In der Program-Datei registrieren Sie AuthenticationStateHandler und fügen Sie den Handler zum IHttpClientFactory hinzu, der HttpClient-Instanzen erstellt.
builder.Services.AddTransient<AuthenticationStateHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<AuthenticationStateHandler>();
Unterstützung für opake Referenzzugriffstoken
In den Anleitungen in diesem Abschnitt wird erläutert, wie Sie eine undurchsichtige (Referenz)-Zugriffstokenunterstützung implementieren, die die folgenden Vorteile gegenüber JSON-Webtoken (JWTs) bietet:
- Strikter Widerruf: Zugriffstoken jederzeit ungültig machen, bevor sie natürlich ablaufen.
- Tokengrößenbeschränkungen: Speichern Sie eine große Anzahl von Benutzeransprüchen im Token, um ein unerschwinglich großes JWT zu vermeiden.
- Sicherheit: Verhindern, dass API-Consumer oder Drittanbieter Zugriffstokenansprüche lesen.
Hinweis
Die folgende Anleitung erfordert einen Authentifizierungsserver, der undurchsichtige Zugriffstoken (Referenztoken) unterstützt. Derzeit unterstützt Microsoft Entra keine undurchsichtige Zugriffstokenüberprüfung. Keycloak und Okta stellen standardmäßig JWT-Zugriffstoken aus. Der undurchsichtige Tokenhandler in diesem Abschnitt funktioniert weiterhin für Keycloak und Okta, da er nur auf RFC 7662-Introspection basiert. „Opak“ bezeichnet in diesem Abschnitt, wie der Client das Token behandelt, und nicht, wie der Server es prägt. Alternativ kann Duende IdentityServer so konfiguriert werden, dass nur undurchsichtige Token ausgegeben werden.
Beim Testen dieses Musters mit Keycloak muss der Introspection-Client der API ein anderer OIDC-Client sein als der, der das Zugriffstoken des Benutzers ausgestellt hat. Die Introspektion eines Tokens mit dem Client, der es ausgestellt hat, liefert {"active": false} zurück, wobei im Serverprotokoll „Access token JWT check failed“ angezeigt wird. Dies geschieht nicht selbstverständlich für das folgende Szenario, da die Blazor Web App und die Minimale API (MinimalApiJwt) separate Clients sind.
AddOpenIdConnect unterstützt undurchsichtige Token, da sie keine Zugriffstokenüberprüfung durchführt, wenn sie für den Autorisierungscodefluss "Proof Key for Code Exchange(PKCE)" konfiguriert ist. Es basiert auf dem HTTPS-Backchannel des ASP.NET Core Servers an den OIDC-Authentifizierungsdienst, um das ID-Token mithilfe des empfangenen Autorisierungscodes abzurufen, wenn der Benutzer nach der Anmeldung zurück zur ASP.NET Core-App umleitet. Wenn die App nur zum Anmelden eines Benutzers mit OIDC zum Abrufen einer gültigen Authentifizierung cookieerforderlich ist, werden undurchsichtige Zugriffstoken unterstützt, ohne die App zu ändern.
Ein Fehler tritt nur auf, wenn das von AddOpenIdConnect abgerufene Opaque-Token an einen anderen Dienst übergeben wird, der versucht, es mit AddJwtBearer zu validieren. Im Gegensatz zu eigenständigen JWTs erfordern undurchsichtige Token eine Anforderung an einen Autorisierungsserver, um den Status zu überprüfen und die Ansprüche abzurufen. Um diese Einschränkung zu umgehen, verwenden Sie entweder eine Drittanbieter-API, z. B. den Duende Introspection Authentication Handler, oder erstellen Sie einen benutzerdefinierten AuthenticationHandler Zum Überprüfen des Tokens.
Important
Duende Software und Okta sind nicht eigentum oder werden von Microsoft kontrolliert und müssen möglicherweise eine Lizenzgebühr für die Produktionsnutzung ihrer Dienste und Bibliotheken bezahlen.
Der folgende AuthenticationHandler<TOptions> und zugehörige Konfigurations- und Hilfscode wird als allgemeiner Ansatz bereitgestellt, der möglicherweise eine weitere Entwicklung erfordert, um den Anforderungen eines bestimmten Autorisierungsservers gerecht zu werden. Der folgende Handler extrahiert das undurchsichtige Token aus dem Authorization Header für einen HTTP-Aufruf an den Introspection-Endpunkt eines Autorisierungsservers und erstellt einen AuthenticationTicket mit den Ansprüchen des Benutzers.
Das Aufrufen des Introspection-Endpunkts eines Autorisierungsservers erfordert eine Authentifizierung. Das folgende Beispiel basiert darauf, das Client-Secret zur Authentifizierung im Authorization-Header der Anfrage (base64-codierte Anmeldeinformationen) mithilfe des Secret Manager-Tools für die lokale Entwicklung und zum Testen zu setzen.
Warnung
Speichern Sie keine geheimen App-Schlüssel, Verbindungszeichenfolgen, Anmeldeinformationen, Kennwörter, persönliche Identifikationsnummern (PINs), privaten C#/.NET-Code oder private Schlüssel/Token im clientseitigen Code, der immer unsicher ist. In Test-/Staging- und Produktionsumgebungen sollten serverseitiger Blazor Code und Web-APIs sichere Authentifizierungsflüsse verwenden, die verhindern, dass Anmeldeinformationen innerhalb von Projektcode- oder Konfigurationsdateien gespeichert werden. Außerhalb der lokalen Entwicklungstests wird empfohlen, die Verwendung von Umgebungsvariablen zum Speichern vertraulicher Daten zu vermeiden, da Umgebungsvariablen nicht der sicherste Ansatz sind. Für lokale Entwicklungstests wird das Tool "Geheimer Manager" zum Sichern vertraulicher Daten empfohlen. Weitere Informationen finden Sie unter "Sichere Verwaltung vertraulicher Daten und Anmeldeinformationen".
Im folgenden Handler verwendet das Client-Secret des Introspection-Endpunkts des Autorisierungsservers den Konfigurationsschlüssel Authentication:Schemes:OpaqueTokenAuthentication:ClientSecret. Berücksichtigen Sie bei Produktions-Apps die Verwendung von Client assertionen. Weitere Informationen finden Sie unter Confidential client assertions (Microsoft Entra documentation).
Wenn das serverprojekt Blazor nicht für das Tool "Geheimer Manager" initialisiert wurde, verwenden Sie eine Befehlsshell, z. B. die PowerShell-Befehlsshell für Entwickler in Visual Studio, um den folgenden Befehl auszuführen. Ändern Sie vor dem Ausführen des Befehls das Verzeichnis mit dem cd Befehl in das Verzeichnis des Serverprojekts. Der Befehl richtet einen bezeichner für geheime Benutzerschlüssel ein (<UserSecretsId> in der Projektdatei der Server-App):
dotnet user-secrets init
Führen Sie den folgenden Befehl aus, um den geheimen Clientschlüssel für den Autorisierungsserver festzulegen. Der {SECRET} Platzhalter ist das Client-Geheimnis:
dotnet user-secrets set "Authentication:Schemes:OpaqueTokenAuthentication:ClientSecret" "{SECRET}"
Wenn Sie Visual Studio verwenden, können Sie bestätigen, dass der geheime Schlüssel festgelegt ist, indem Sie in Projektmappen-Explorer mit der rechten Maustaste auf das Serverprojekt klicken und "Benutzergeheimnisse verwalten" auswählen.
Extensions/HttpRequestExtensions.cs:
namespace MinimalApiJwt.Extensions;
public static class HttpRequestExtensions
{
public static string? ExtractBearerToken(this HttpRequest request)
{
var authorizationHeader = request.Headers.Authorization.ToString();
if (!string.IsNullOrEmpty(authorizationHeader) &&
authorizationHeader.StartsWith("Bearer ",
StringComparison.OrdinalIgnoreCase))
{
var token = authorizationHeader["Bearer ".Length..].Trim();
if (!string.IsNullOrEmpty(token))
{
return token;
}
}
return null;
}
}
Authentication/OpaqueTokenAuthenticationOptions.cs:
using Microsoft.AspNetCore.Authentication;
namespace MinimalApiJwt.Authentication;
public class OpaqueTokenAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "OpaqueTokenAuthentication";
public string? IntrospectionEndpoint { get; set; }
public string? ClientId { get; set; }
public string? ClientSecret { get; set; }
}
Der folgende Handler versucht, ein undurchsichtiges Zugriffstoken (Verweis) zu überprüfen. Es wird ein HTTP-Aufruf an den Introspection-Endpunkt des Autorisierungsservers mit dem Token und den Anmeldeinformationen der API ausgeführt. Die Antwort wird verarbeitet, um festzustellen, ob das Token gültig ist:
- Wenn das Token gültig ist, wird ein AuthenticationTicket Objekt erstellt, das die Ansprüche des Benutzers enthält.
- Wenn das Token ungültig ist, wird ein fehlgeschlagenes Autorisierungsergebnis zurückgegeben.
Die Optionen des Handlers (Options) sind eine vom Basistyp AuthenticationHandler<TOptions> bereitgestellte Instanz von OpaqueTokenAuthenticationOptions, die in der Program-Datei der App mit dem Introspektionsendpunkt des Autorisierungsservers und der Client-ID der API konfiguriert wird. Der geheime Clientschlüssel der API wird während der Entwicklung vom Geheimen Manager-Tool bereitgestellt.
IOptionsMonitor<OpaqueTokenAuthenticationOptions> (optionsMonitor) wird nicht direkt vom Handler verwendet, kann jedoch verwendet werden, um dynamische Konfigurationsänderungen zur Laufzeit zu unterstützen.
Für den Inhalt der Anforderung in FormUrlEncodedContent benötigen einige Server einen Tokentyphinweis (token_type_hint). Der erforderliche Wert kann z. B. sein access_token. Ausführliche Informationen finden Sie in der Dokumentation des Authentifizierungsservers.
Authentication/OpaqueTokenAuthenticationHandler.cs:
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using MinimalApiJwt.Extensions;
namespace MinimalApiJwt.Authentication;
public class OpaqueTokenAuthenticationHandler(
IOptionsMonitor<OpaqueTokenAuthenticationOptions> optionsMonitor,
ILoggerFactory logger,
UrlEncoder encoder,
IHttpClientFactory httpClientFactory)
: AuthenticationHandler<OpaqueTokenAuthenticationOptions>(optionsMonitor,
logger, encoder)
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var opaqueToken = Request.ExtractBearerToken();
if (opaqueToken is null)
{
var failedResult = AuthenticateResult.Fail(
"Bearer token not found in Authorization header.");
return failedResult;
}
var introspectionUri = Options.IntrospectionEndpoint;
var clientId = Options.ClientId;
var clientSecret = Options.ClientSecret;
if (string.IsNullOrWhiteSpace(introspectionUri) ||
string.IsNullOrWhiteSpace(clientId) ||
string.IsNullOrWhiteSpace(clientSecret))
{
var failedResult = AuthenticateResult.Fail(
"Opaque token authentication isn't fully configured.");
return failedResult;
}
using var client = httpClientFactory.CreateClient();
// Set the Authorization header (base64 encoded credentials)
var authString = Convert.ToBase64String(
System.Text.Encoding.ASCII.GetBytes($"{clientId}:{clientSecret}"));
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", authString);
// Prepare the form-encoded body containing the token
var content = new FormUrlEncodedContent(
[
new KeyValuePair<string, string>("token", opaqueToken)
]);
// Post to the introspection endpoint
var response = await client.PostAsync(introspectionUri, content);
if (!response.IsSuccessStatusCode)
{
var failedResult = AuthenticateResult.Fail(
"Introspection endpoint failure.");
return failedResult;
}
// Parse the JSON response
var responseString = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(responseString);
// The 'active' property determines if the token is valid and not expired
var tokenIsValid =
doc.RootElement.TryGetProperty("active", out var activeProperty) &&
activeProperty.ValueKind == JsonValueKind.True;
if (tokenIsValid)
{
// Map standard introspection response fields onto claims.
// Field names below match what Keycloak, Duende IdentityServer,
// Auth0, and Okta return; adjust the role source for your provider.
var claims = new List<Claim>();
string? Get(string name) =>
doc.RootElement.TryGetProperty(name, out var v) &&
v.ValueKind == JsonValueKind.String ? v.GetString() : null;
var sub = Get("sub");
var username = Get("preferred_username") ?? Get("username") ?? sub;
if (sub is not null) claims.Add(new Claim(ClaimTypes.NameIdentifier, sub));
if (username is not null) claims.Add(new Claim(ClaimTypes.Name, username));
if (Get("email") is { } email) claims.Add(new Claim(ClaimTypes.Email, email));
if ((Get("client_id") ?? Get("azp")) is { } cid)
claims.Add(new Claim("client_id", cid));
if (Get("scope") is { } scope)
foreach (var s in scope.Split(' ', StringSplitOptions.RemoveEmptyEntries))
claims.Add(new Claim("scope", s));
// Keycloak surfaces realm roles under realm_access.roles.
// Duende/IdentityServer uses a flat "role" claim; Auth0 uses a
// configurable custom claim. Adjust for your authorization server.
if (doc.RootElement.TryGetProperty("realm_access", out var ra) &&
ra.ValueKind == JsonValueKind.Object &&
ra.TryGetProperty("roles", out var roles) &&
roles.ValueKind == JsonValueKind.Array)
{
foreach (var r in roles.EnumerateArray())
if (r.ValueKind == JsonValueKind.String)
claims.Add(new Claim(ClaimTypes.Role, r.GetString()!));
}
var identity = new ClaimsIdentity(claims,
OpaqueTokenAuthenticationOptions.DefaultScheme,
nameType: ClaimTypes.Name,
roleType: ClaimTypes.Role);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal,
OpaqueTokenAuthenticationOptions.DefaultScheme);
var result = AuthenticateResult.Success(ticket);
return result;
}
else
{
var failedResult = AuthenticateResult.Fail("Bearer token invalid.");
return failedResult;
}
}
}
Hinweis
Der vorherige Ansatz kann weiter verbessert werden, indem der OpenID Connect-Ermittlungsendpunkt verwendet und ein Cache für die Introspection-Anforderungen des HttpClient Clients hinzugefügt wird.
In der Program-Datei:
using MinimalApiJwt.Authentication;
...
builder.Services.AddHttpClient();
builder.Services.AddAuthentication()
.AddScheme<OpaqueTokenAuthenticationOptions, OpaqueTokenAuthenticationHandler>(
OpaqueTokenAuthenticationOptions.DefaultScheme,
options =>
{
options.IntrospectionEndpoint = "{AUTH SERVER INTROSPECTION URI}";
options.ClientId = "{API CLIENT ID}";
options.ClientSecret =
builder.Configuration[
"Authentication:Schemes:OpaqueTokenAuthentication:ClientSecret"];
});
Platzhalter des vorherigen Beispiels:
-
{AUTH SERVER INTROSPECTION URI}: Introspection-URI des Authentifizierungsservers -
{API CLIENT ID}: API-Client-ID
Werte für den Authentifizierungsserver-Introspektion-URI ({AUTH SERVER INTROSPECTION URI}) und die API-Client-ID ({API CLIENT ID}) können über App-Einstellungen oder eine andere Konfigurationsquelle bereitgestellt werden.
Tokens werden in der Regel bei einem Abmeldeereignis über den Widerrufs-Endpunkt ungültig gemacht. Das folgende Beispiel ist ein Ausgangspunkt für die Weiterentwicklung:
app.MapPost("/logout",
async ([FromForm] string? returnUrl, HttpContext context,
IHttpClientFactory httpClientFactory) =>
{
var accessToken = await context.GetTokenAsync("access_token");
if (!string.IsNullOrEmpty(accessToken))
{
// Prepare the revocation request (RFC 7009)
var content =
new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "token", accessToken },
{ "token_type_hint", "access_token" },
{ "client_id", "{API CLIENT ID}" },
{ "client_secret", "{CLIENT SECRET}" }
});
// POST to the revocation endpoint
using var client = httpClientFactory.CreateClient();
await client.PostAsync("{AUTH SERVER TOKEN REVOCATION URI}", content);
}
return TypedResults.SignOut(new AuthenticationProperties { RedirectUri = "{REDIRECT URI}" },
[CookieAuthenticationDefaults.AuthenticationScheme]);
});
Platzhalter des vorherigen Beispiels:
-
{AUTH SERVER TOKEN REVOCATION URI}: Der Tokensperr-URI des Authentifizierungsservers. -
{API CLIENT ID}: Die API-Client-ID. -
{CLIENT SECRET}: Das sicher abgerufene Client-Geheimnis. -
{REDIRECT URI}: Die Umleitungs-URI.
In Duende IdentityServer werden Token automatisch widerrufen, indem die Clientkonfigurationseigenschaft CoordinateLifetimeWithUserSession auf true gesetzt wird, wodurch die zugehörigen Token automatisch entfernt werden, wenn eine Sitzung endet. Weitere Informationen finden Sie unter Sitzungsbereinigung und Abmeldung (Duende-Dokumentation).
Die integrierte, undurchsichtige Zugriffstokenunterstützung wird für eine zukünftige Version von .NET berücksichtigt. Weitere Informationen finden Sie unter Opaque – Referenztokenüberprüfung (dotnet/aspnetcore #46026).