Aufrufen nachgeschalteter APIs aus Web-Apps

In diesem Handbuch wird erläutert, wie Downstream-APIs aus ASP.NET Core- und OWIN-Webanwendungen mithilfe von Microsoft.Identity.Web aufgerufen werden. In Web-Apps erwerben Sie Token im Namen des angemeldeten Benutzers , um APIs mit delegierten Berechtigungen aufzurufen.

Verständnis des Tokenflusses

Wenn sich ein Benutzer bei Ihrer Webanwendung anmeldet, können Sie nachgeschaltete APIs (Microsoft Graph, Azure Dienste oder benutzerdefinierte APIs) in ihrem Namen aufrufen. Microsoft. Identity.Web verarbeitet Tokenakquisition, Zwischenspeicherung und automatische Aktualisierung.

Überprüfung des Benutzertokenflusses

sequenceDiagram
    participant User as User Browser
    participant WebApp as Your Web App
    participant AzureAD as Microsoft Entra ID
    participant API as Downstream API

    User->>WebApp: 1. Access page requiring API data
    Note over WebApp: User already signed in
    WebApp->>AzureAD: 2. Request access token for API<br/>(using user's refresh token)
    AzureAD->>AzureAD: 3. Validate & check consent
    AzureAD->>WebApp: 4. Return access token
    Note over WebApp: Cache token
    WebApp->>API: 5. Call API with token
    API->>WebApp: 6. Return data
    WebApp->>User: 7. Render page with data

Überprüfen der Voraussetzungen

Stellen Sie sicher, dass Ihre Umgebung die folgenden Anforderungen erfüllt, bevor Sie beginnen.

  • Web-App mit OpenID Connect-Authentifizierung konfiguriert
  • Benutzeranmeldung funktioniert
  • App-Registrierung mit konfigurierten API-Berechtigungen
  • Benutzerzustimmung erhalten (oder Administratorzustimmung erteilt)

Implementieren von ASP.NET Core

1. Konfigurieren der Authentifizierung und des Tokenerwerbs

Fügen Sie Authentifizierungsdienste hinzu, und aktivieren Sie die Tokenakquisition in Ihrer Program.cs Datei.

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add authentication with explicit scheme
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddRazorPages()
    .AddMicrosoftIdentityUI();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();
app.Run();

2. Konfigurieren von appsettings.json

Definieren Sie Ihre Microsoft Entra ID-App-Registrierung und nachgeschaltete API-Einstellungen in appsettings.json.

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "CallbackPath": "/signin-oidc",
    "SignedOutCallbackPath": "/signout-callback-oidc",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "your-client-secret"
      }
    ]
  },
  "DownstreamApis": {
    "GraphAPI": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": ["user.read", "mail.read"]
    },
    "MyAPI": {
      "BaseUrl": "https://myapi.example.com",
      "Scopes": ["api://my-api-id/access_as_user"]
    }
  }
}

Wichtig: Für Web-Apps, die nachgeschaltete APIs aufrufen, benötigen Sie zusätzlich zur Anmeldekonfiguration Clientanmeldeinformationen (Zertifikat oder geheimer Schlüssel).

3. Hinzufügen der Nachgelagerten API-Unterstützung

Wählen Sie eine der folgenden Optionen aus, um Ihre downstream-APIs zu registrieren.

Option A: Registrieren benannter APIs

Der folgende Code registriert mehrere downstream-APIs aus der Konfiguration.

using Microsoft.Identity.Web;

// Register multiple downstream APIs
builder.Services.AddDownstreamApis(
    builder.Configuration.GetSection("DownstreamApis"));

Option B: Microsoft Graph Hilfsprogramm verwenden

Der folgende Code registriert den Microsoft Graph SDK-Client aus der Konfiguration.

// Install: Microsoft.Identity.Web.GraphServiceClient
builder.Services.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApis:GraphAPI"));

4. Aufrufen der downstream-API vom Controller

Fügen Sie IDownstreamApi in Ihren Controller ein, und rufen Sie die API im Namen des angemeldeten Benutzers auf.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
public class ProfileController : Controller
{
    private readonly IDownstreamApi _downstreamApi;
    private readonly ILogger<ProfileController> _logger;

    public ProfileController(
        IDownstreamApi downstreamApi,
        ILogger<ProfileController> logger)
    {
        _downstreamApi = downstreamApi;
        _logger = logger;
    }

    public async Task<IActionResult> Index()
    {
        try
        {
            // Call downstream API on behalf of user
            var userData = await _downstreamApi.GetForUserAsync<UserData>(
                "MyAPI",
                options => options.RelativePath = "api/profile");

            return View(userData);
        }
        catch (MicrosoftIdentityWebChallengeUserException ex)
        {
            // Incremental consent required
            // Redirect user to consent page
            return Challenge(
                new AuthenticationProperties
                {
                    RedirectUri = "/Profile"
                },
                OpenIdConnectDefaults.AuthenticationScheme);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to call downstream API");
            return View("Error");
        }
    }
}

5. Aufrufen der downstream-API von Razor Page

Fügen Sie IDownstreamApi in das Razor Page-Modell ein und rufen Sie die API auf.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
public class ProfileModel : PageModel
{
    private readonly IDownstreamApi _downstreamApi;

    public UserData UserData { get; set; }

    public ProfileModel(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task OnGetAsync()
    {
        try
        {
            UserData = await _downstreamApi.GetForUserAsync<UserData>(
                "MyAPI",
                options => options.RelativePath = "api/profile");
        }
        catch (MicrosoftIdentityWebChallengeUserException)
        {
            // Handle incremental consent
            // User will be redirected to consent page
            throw;
        }
    }
}

Microsoft Graph aufrufen

Verwenden Sie für Microsoft Graph-API-Anrufe die dedizierte GraphServiceClient.

Pakete installieren

Installieren Sie das Microsoft Graph-Paket für Microsoft. Identity.Web.

dotnet add package Microsoft.Identity.Web.GraphServiceClient

Konfigurieren Sie den Graph-Client im Startcode.

// Startup configuration
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraph(options =>
    {
        options.Scopes = "user.read mail.read";
    })
    .AddInMemoryTokenCaches();

Rufen Sie die Graph-API an

Injizieren Sie GraphServiceClient in Ihren Controller, um Microsoft Graph Endpunkte aufzurufen.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;

[Authorize]
{
    private readonly GraphServiceClient _graphClient;

    public HomeController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }

    public async Task<IActionResult> Index()
    {
        // Get current user's profile
        var user = await _graphClient.Me.GetAsync();

        // Get user's emails
        var messages = await _graphClient.Me.Messages
            .GetAsync(config => config.QueryParameters.Top = 10);

        return View(new { User = user, Messages = messages });
    }
}

Erfahren Sie mehr über Microsoft Graph Integration


Azure SDK-Clients anrufen

Verwenden Sie zum Aufrufen von Azure-Diensten MicrosoftIdentityTokenCredential.

Pakete installieren

Installieren Sie die erforderlichen Azure SDK Pakete.

dotnet add package Microsoft.Identity.Web.Azure
dotnet add package Azure.Storage.Blobs

Registrieren Sie die Microsoft Entra Tokenanmeldedaten im Startcode.

using Microsoft.Identity.Web;

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

// Add Azure token credential
builder.Services.AddMicrosoftIdentityAzureTokenCredential();

Zugreifen auf Azure-Dienste

Fügen Sie die Tokenanmeldeinformationen ein, und verwenden Sie sie mit Azure SDK Clients.

using Azure.Storage.Blobs;
using Microsoft.Identity.Web;

public class StorageController : Controller
{
    private readonly MicrosoftIdentityTokenCredential _credential;

    public StorageController(MicrosoftIdentityTokenCredential credential)
    {
        _credential = credential;
    }

    [Authorize]
    public async Task<IActionResult> ListBlobs()
    {
        var blobClient = new BlobServiceClient(
            new Uri("https://myaccount.blob.core.windows.net"),
            _credential);

        var container = blobClient.GetBlobContainerClient("mycontainer");
        var blobs = new List<string>();

        await foreach (var blob in container.GetBlobsAsync())
        {
            blobs.Add(blob.Name);
        }

        return View(blobs);
    }
}

Erfahren Sie mehr über Azure SDK Integration


Aufrufen benutzerdefinierter APIs mit IDownstreamApi

Für Ihre eigenen REST-APIs, IDownstreamApi bietet ein einfacher, konfigurationsgesteuerter Ansatz.

Konfigurieren der API

Definieren Sie die nachgeschalteten API-Einstellungen in appsettings.json.

{
  "DownstreamApis": {
    "MyAPI": {
      "BaseUrl": "https://myapi.example.com",
      "Scopes": ["api://my-api-id/access_as_user"],
      "RequestAppToken": false
    }
  }
}

GET-Anforderungen senden

Abrufen von Daten aus der downstream-API mit optionalen Abfrageparametern.

// Simple GET
var data = await _downstreamApi.GetForUserAsync<MyData>(
    "MyAPI",
    options => options.RelativePath = "api/resource");

// GET with query parameters
var results = await _downstreamApi.GetForUserAsync<SearchResults>(
    "MyAPI",
    options =>
    {
        options.RelativePath = "api/search";
        options.QueryParameters = new Dictionary<string, string>
        {
            ["query"] = "test",
            ["limit"] = "10"
        };
    });

Senden von POST-Anfragen

Erstellen Sie eine neue Ressource für die downstream-API, indem Sie einen Anforderungstext posten.

var newItem = new CreateItemRequest
{
    Name = "New Item",
    Description = "Item description"
};

var created = await _downstreamApi.PostForUserAsync<CreateItemRequest, CreatedItem>(
    "MyAPI",
    newItem,
    options => options.RelativePath = "api/items");

PUT- und DELETE-Anforderungen senden

Aktualisieren oder Löschen von Ressourcen in der downstream-API.

// PUT request
var updated = await _downstreamApi.PutForUserAsync<UpdateRequest, UpdatedItem>(
    "MyAPI",
    updateData,
    options => options.RelativePath = "api/items/123");

// DELETE request
await _downstreamApi.DeleteForUserAsync(
    "MyAPI",
    null,
    options => options.RelativePath = "api/items/123");

Weitere Informationen zu benutzerdefinierten API-Aufrufen


Verwenden von IAuthorizationHeaderProvider (erweitert)

Verwenden Sie IAuthorizationHeaderProviderfür die maximale Kontrolle über HTTP-Anforderungen .

Registrieren des HTTP-Clients

Registrieren Sie einen benannten HTTP-Client für Ihre downstream-API.

builder.Services.AddHttpClient("MyAPI", client =>
{
    client.BaseAddress = new Uri("https://myapi.example.com");
});

Erstellen benutzerdefinierter HTTP-Anforderungen

Erstellen und Senden von HTTP-Anforderungen mit benutzerdefinierten Headern und Autorisierungen.

using Microsoft.Identity.Abstractions;

public class CustomApiService
{
    private readonly IAuthorizationHeaderProvider _authProvider;
    private readonly IHttpClientFactory _httpClientFactory;

    public CustomApiService(
        IAuthorizationHeaderProvider authProvider,
        IHttpClientFactory httpClientFactory)
    {
        _authProvider = authProvider;
        _httpClientFactory = httpClientFactory;
    }

    public async Task<MyData> GetDataAsync()
    {
        // Get authorization header
        var authHeader = await _authProvider.CreateAuthorizationHeaderForUserAsync(
            new[] { "api://my-api-id/access_as_user" });

        // Create HTTP request with custom logic
        var client = _httpClientFactory.CreateClient("MyAPI");
        var request = new HttpRequestMessage(HttpMethod.Get, "api/resource");
        request.Headers.Add("Authorization", authHeader);
        request.Headers.Add("X-Custom-Header", "custom-value");

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<MyData>();
    }
}

Weitere Informationen zur benutzerdefinierten HTTP-Logik


Beim Aufrufen nachgeschalteter APIs muss Ihre Anwendung möglicherweise Szenarien behandeln, in denen benutzerinteraktion erforderlich ist. Dies geschieht in drei Hauptszenarien:

  1. Inkrementelle Zustimmung – Anfordern zusätzlicher Berechtigungen über das, was ursprünglich erteilt wurde
  2. Bedingter Zugriff – Erfüllen von Sicherheitsanforderungen wie MFA, Gerätekompatibilität oder Standortrichtlinien
  3. Token Cache Eviction – Wiederbefüllung des Token-Caches nach Neustart der Anwendung oder Ablauf des Caches

Microsoft. Identity.Web bietet eine automatische Behandlung dieser Szenarien mit minimalem Code erforderlich.

Verstehen des Flusses

Wenn Microsoft. Identity.Web erkennt, dass eine Benutzerinteraktion erforderlich ist, wird ein MicrosoftIdentityWebChallengeUserException ausgelöst. Das Framework verarbeitet dies automatisch über das [AuthorizeForScopes] Attribut oder den MicrosoftIdentityConsentAndConditionalAccessHandler Dienst (für Blazor), der:

  1. Leitet den Benutzer zur Microsoft Entra ID zur Zustimmung/Authentifizierung um.
  2. Behält die ursprüngliche Anforderungs-URL bei
  3. Gibt den Benutzer nach Abschluss des Prozesses an sein beabsichtigtes Ziel zurück.
  4. Speichert die neu erworbenen Token.

Überprüfen der Voraussetzungen

Um die automatische Zustimmungsbehandlung zu aktivieren, stellen Sie sicher, dass Program.cs die folgende Konfiguration enthält.

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("MyAPI", builder.Configuration.GetSection("MyAPI"))
    .AddInMemoryTokenCaches();

// For MVC applications - enables the account controller
builder.Services.AddControllersWithViews()
    .AddMicrosoftIdentityUI();

// Ensure routes are mapped
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers(); // Required for AccountController

Anwenden von [AuthorizeForScopes] in MVC-Controllern

Das [AuthorizeForScopes] Attribut, das für Controller- oder Controlleraktionen festgelegt wird, verarbeitet MicrosoftIdentityWebChallengeUserException automatisch, indem der Benutzer gefragt wird, wenn zusätzliche Berechtigungen erforderlich sind.

Geltungsbereiche inline deklarieren

Geben Sie die erforderlichen Bereiche direkt im Attribut an.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public class ProfileController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public ProfileController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        // AuthorizeForScopes automatically handles consent challenges
        var userData = await _downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");

        return View(userData);
    }

    // Different action requires additional scopes
    [AuthorizeForScopes(Scopes = new[] { "user.read", "mail.read" })]
    public async Task<IActionResult> Emails()
    {
        var emails = await _downstreamApi.GetForUserAsync<EmailList>(
            "GraphAPI",
            options => options.RelativePath = "me/messages");

        return View(emails);
    }
}

Konfigurieren von Bereichen aus appsettings

Speichern Sie Bereiche in appsettings.json, um die Wartbarkeit zu verbessern.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "common",
    "ClientId": "[Your-Client-ID]",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "[Your-Client-Secret]"
      }
    ]
  },
  "DownstreamApis": {
    "TodoList": {
      "BaseUrl": "https://localhost:5001",
      "Scopes": [ "api://[API-Client-ID]/access_as_user" ]
    },
    "GraphAPI": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": [ "https://graph.microsoft.com/Mail.Read", "https://graph.microsoft.com/Mail.Send" ]
    }
  }
}

Controller:

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:TodoList:Scopes:0")]
public class TodoListController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public TodoListController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        var todos = await _downstreamApi.GetForUserAsync<IEnumerable<TodoItem>>(
            "TodoList",
            options => options.RelativePath = "api/todolist");

        return View(todos);
    }

    [AuthorizeForScopes(ScopeKeySection = "DownstreamApis:GraphAPI:Scopes:0")]
    public async Task<IActionResult> EmailTodos()
    {
        // If user hasn't consented to Mail.Send, they'll be prompted
        await _downstreamApi.PostForUserAsync<EmailMessage, object>(
            "GraphAPI",
            new EmailMessage { /* ... */ },
            options => options.RelativePath = "me/sendMail");

        return RedirectToAction("Index");
    }
}

Konfigurieren von Microsoft Entra External ID mit Benutzerflüssen

Geben Sie für B2C-Anwendungen (External ID) mit mehreren Benutzerflüssen den Benutzerfluss im Attribut an.

[Authorize]
public class AccountController : Controller
{
    private const string SignUpSignInFlow = "b2c_1_susi";
    private const string EditProfileFlow = "b2c_1_edit_profile";
    private const string ResetPasswordFlow = "b2c_1_reset";

    [AuthorizeForScopes(
        ScopeKeySection = "DownstreamApis:TodoList:Scopes:0",
        UserFlow = SignUpSignInFlow)]
    public async Task<IActionResult> Index()
    {
        var data = await _downstreamApi.GetForUserAsync<UserData>(
            "TodoList",
            options => options.RelativePath = "api/data");

        return View(data);
    }

    [AuthorizeForScopes(
        Scopes = new[] { "openid", "offline_access" },
        UserFlow = EditProfileFlow)]
    public async Task<IActionResult> EditProfile()
    {
        // This triggers the B2C edit profile flow
        return RedirectToAction("Index");
    }
}

[AuthorizeForScopes] auf Razor-Seiten anwenden

Wenden Sie [AuthorizeForScopes] auf die Seitenmodellklasse an.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:MyAPI:Scopes:0")]
public class IndexModel : PageModel
{
    private readonly IDownstreamApi _downstreamApi;

    public UserData UserData { get; set; }

    public IndexModel(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task OnGetAsync()
    {
        // Automatically handles consent challenges
        UserData = await _downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");
    }
}

Blazor Server-Anwendungen erfordern eine explizite Ausnahmebehandlung mithilfe des Diensts MicrosoftIdentityConsentAndConditionalAccessHandler .

Konfigurieren von Program.cs

Registrieren Sie den Zustimmungshandler für Blazor Server in Ihrem Startcode.

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApis("TodoList", builder.Configuration.GetSection("DownstreamApis"))
    .AddInMemoryTokenCaches();

// Register the consent handler for Blazor
builder.Services.AddServerSideBlazor()
    .AddMicrosoftIdentityConsentHandler();

Erstellen Sie die Blazor-Komponente

Verpacken Sie API-Aufrufe in Try-Catch-Blöcken und verwenden Sie ConsentHandler.HandleException(), um Einwilligungsherausforderungen zu bewältigen.

@page "/todolist"
@using Microsoft.Identity.Web
@using Microsoft.Identity.Abstractions
@using MyApp.Models

@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
@inject IDownstreamApi DownstreamApi

<h3>My Todo List</h3>

@if (todos == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <ul>
        @foreach (var todo in todos)
        {
            <li>@todo.Title</li>
        }
    </ul>
}

@code {
    private IEnumerable<TodoItem> todos;

    protected override async Task OnInitializedAsync()
    {
        await LoadTodosAsync();
    }

    [AuthorizeForScopes(ScopeKeySection = "DownstreamApis:TodoList:Scopes:0")]
    private async Task LoadTodosAsync()
    {
        try
        {
            todos = await DownstreamApi.GetForUserAsync<IEnumerable<TodoItem>>(
                "TodoList",
                options => options.RelativePath = "api/todolist");
        }
        catch (Exception ex)
        {
            // Handles MicrosoftIdentityWebChallengeUserException
            // and initiates user consent/authentication flow
            ConsentHandler.HandleException(ex);
        }
    }

    private async Task AddTodoAsync(string title)
    {
        try
        {
            await DownstreamApi.PostForUserAsync<TodoItem, TodoItem>(
                "TodoList",
                new TodoItem { Title = title },
                options => options.RelativePath = "api/todolist");

            await LoadTodosAsync();
        }
        catch (Exception ex)
        {
            ConsentHandler.HandleException(ex);
        }
    }
}

Manuelles Behandeln von Ausnahmen (erweitert)

Wenn Sie eine benutzerdefinierte Zustimmungsflusslogik benötigen, behandeln Sie MicrosoftIdentityWebChallengeUserException explizit Folgendes:

[Authorize]
public class AdvancedController : Controller
{
    private readonly IDownstreamApi _downstreamApi;
    private readonly ILogger<AdvancedController> _logger;

    public AdvancedController(
        IDownstreamApi downstreamApi,
        ILogger<AdvancedController> logger)
    {
        _downstreamApi = downstreamApi;
        _logger = logger;
    }

    public async Task<IActionResult> SendEmail()
    {
        try
        {
            await _downstreamApi.PostForUserAsync<EmailMessage, object>(
                "GraphAPI",
                new EmailMessage
                {
                    Subject = "Test",
                    Body = "Test message"
                },
                options => options.RelativePath = "me/sendMail");

            return RedirectToAction("Success");
        }
        catch (MicrosoftIdentityWebChallengeUserException ex)
        {
            // Log the consent requirement
            _logger.LogWarning(
                "Consent required for scopes: {Scopes}. Challenging user.",
                string.Join(", ", ex.Scopes));

            // Custom properties for redirect
            var properties = new AuthenticationProperties
            {
                RedirectUri = Url.Action("SendEmail", "Advanced"),
            };

            // Add custom state if needed
            properties.Items["consent_attempt"] = "1";

            return Challenge(properties, OpenIdConnectDefaults.AuthenticationScheme);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to send email");
            return View("Error");
        }
    }
}

Bearbeitung von Szenarien für bedingten Zugriff

Richtlinien für bedingten Zugriff können zusätzliche Authentifizierungsfaktoren erfordern. Die Verarbeitung ist identisch mit der inkrementellen Zustimmung:

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:SecureAPI:Scopes:0")]
public class SecureDataController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public SecureDataController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        // If conditional access requires MFA, AuthorizeForScopes
        // automatically challenges the user
        var sensitiveData = await _downstreamApi.GetForUserAsync<SensitiveData>(
            "SecureAPI",
            options => options.RelativePath = "api/sensitive");

        return View(sensitiveData);
    }
}

Häufige Trigger für bedingten Zugriff:

  • Mehrstufige Authentifizierung (MFA)
  • Kompatible Geräteanforderung
  • Vertrauenswürdiger Netzwerkstandort
  • Zustimmung zu den Nutzungsbedingungen
  • Kennwortänderungsanforderung

Bewährte Methoden befolgen

Wenden Sie diese Empfehlungen an, wenn Sie die Zustimmungs- und bedingten Zugriffsbehandlung implementieren.

Verwenden [AuthorizeForScopes] - Einfachster Ansatz für MVC-Controller und Razor Pages

Scopes in der Konfiguration speichern - Verwenden Sie ScopeKeySection = "DownstreamApis:ApiName:Scopes:0", um auf die Scopes in appsettings.json zu referenzieren.

Anwenden auf Controllerebene – Festlegen von Standardbereichen auf dem Controller, Außerkraftsetzen für bestimmte Aktionen

Behandeln von Ausnahmen in Blazor – API-Aufrufe immer mit Try-Catch umschließen und verwenden ConsentHandler.HandleException()

Erneutes Auslösen von Ausnahmen - Wenn Sie MicrosoftIdentityWebChallengeUserException abfangen, lösen Sie es erneut aus, damit [AuthorizeForScopes] es verarbeiten kann.

Testen des bedingten Zugriffs – Überprüfen, ob Ihre App MFA und andere Zertifizierungsstellenrichtlinien ordnungsgemäß verarbeitet

Ausnahmen nicht unterdrücken – Abfangen ohne sie erneut auszulösen, unterbricht den Zustimmungsfluss.

Keine Antworten auf unbestimmte Zeit zwischenspeichern – Token laufen ab; Entwurf für die erneute Authentifizierung


Statische Berechtigungen (Administratorzustimmung)

Alle Berechtigungen werden während der App-Registrierung angefordert und von einem Mandantenadministrator genehmigt.

Vorteile:

  • Benutzer sehen keine Zustimmungsaufforderungen
  • Erforderlich für Erstanbieter-Microsoft-Apps
  • Einfachere Benutzererfahrung

Nachteile:

  • Erfordert die Beteiligung des Mandantenadministrators
  • Überprivilegiert von Anfang an
  • Weniger flexibel für Szenarien mit mehreren Mandanten

Configuration:

// Request all pre-approved scopes for Microsoft Graph
var scopes = new[] { "https://graph.microsoft.com/.default" };

var userData = await _downstreamApi.GetForUserAsync<UserData>(
    "GraphAPI",
    options =>
    {
        options.RelativePath = "me";
        options.Scopes = scopes; // Use .default scope
    });

Inkrementelle Zustimmung (dynamisch)

Berechtigungen werden während der Laufzeit nach Bedarf angefordert:

Vorteile:

  • Bessere Sicherheit (Prinzip der geringsten Rechte)
  • Benutzer stimmen zu, was sie tatsächlich verwenden
  • Funktioniert für Mehrinstanzen-Apps

Nachteile:

  • Benutzer können mit Zustimmungsaufforderungen unterbrochen werden.
  • Erfordert Bearbeitung MicrosoftIdentityWebChallengeUserException

Empfehlung: Verwenden Sie die inkrementelle Zustimmung für Anwendungen mit mehreren Mandanten; Verwenden statischer Berechtigungen für Unternehmens-Apps von Erstanbietern, bei denen die Administratorzustimmung garantiert ist


Tokenzwischenspeicherung konfigurieren

Microsoft. Identity.Web speichert Token zwischen, um die Leistung zu verbessern und Aufrufe von Microsoft Entra zu reduzieren.

In-Memory-Cache verwenden (Standard)

Fügen Sie einen Cache für In-Memory-Token für Entwicklungs- oder Einzelserverszenarien hinzu.

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches(); // In-memory cache

Verwendung für:

  • Entwicklung
  • Bereitstellungen mit einem einzelnen Server
  • Kleine Benutzerbasis

Limitations:

  • Nicht instanzenübergreifend geteilt
  • Beim Neustart der App verloren
  • Der Arbeitsspeicherverbrauch wächst mit Benutzern.

Konfigurieren Sie einen verteilten Cache wie Redis oder SQL Server für Produktionsbereitstellungen.

// Install: Microsoft.Identity.Web.TokenCache

// Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration["Redis:ConnectionString"];
    options.InstanceName = "MyApp_";
});

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDistributedTokenCaches();

// SQL Server
builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration["SqlCache:ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TokenCache";
});

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDistributedTokenCaches();

Verwendung für:

  • Bereitstellungen mit mehreren Servern (Lastenausgleich)
  • Hochverfügbarkeitsszenarien
  • Große Benutzerbasis
  • Beständiger Cache über Neustarts hinweg

Umgang mit Tokenerwerbsfehlern

Allgemeine Ausnahmen abfangen

Der folgende Code veranschaulicht, wie die am häufigsten verwendeten Tokenakquisitions-Ausnahmen erfasst und behandelt werden.

try
{
    var data = await _downstreamApi.GetForUserAsync<MyData>(
        "MyAPI",
        options => options.RelativePath = "api/resource");
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
    // User needs to consent or reauthenticate
    _logger.LogWarning($"User consent required: {ex.Message}");
    return Challenge(new AuthenticationProperties { RedirectUri = Request.Path });
}
catch (MsalUiRequiredException ex)
{
    // User interaction required (sign-in again, MFA, etc.)
    _logger.LogWarning($"User interaction required: {ex.Message}");
    return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
}
catch (MsalServiceException ex)
{
    // Service error (Microsoft Entra ID unavailable, etc.)
    _logger.LogError(ex, "Microsoft Entra ID service error");
    return StatusCode(503, "Authentication service temporarily unavailable");
}
catch (HttpRequestException ex)
{
    // Downstream API unreachable
    _logger.LogError(ex, "Downstream API call failed");
    return StatusCode(503, "Downstream service unavailable");
}

Implementierung von stufenweiser Verschlechterung

Laden Sie optionale Daten aus nachgeschalteten APIs, und greifen Sie auf Standardwerte zurück, wenn Aufrufe fehlschlagen.

public async Task<IActionResult> Dashboard()
{
    var model = new DashboardModel();

    // Try to load optional data from downstream API
    try
    {
        model.EnrichedData = await _downstreamApi.GetForUserAsync<EnrichedData>(
            "MyAPI",
            options => options.RelativePath = "api/enriched");
    }
    catch (Exception ex)
    {
        _logger.LogWarning(ex, "Failed to load enriched data, using defaults");
        model.EnrichedData = new EnrichedData { /* defaults */ };
    }

    return View(model);
}

Implementieren von OWIN (.NET Framework)

Führen Sie für OWIN-basierte Webanwendungen im .NET Framework die folgenden Schritte aus.

1. Installieren von Paketen

Installieren Sie die erforderlichen NuGet-Pakete.

Install-Package Microsoft.Identity.Web.OWIN
Install-Package Microsoft.Owin.Host.SystemWeb

2. Konfigurieren des Startvorgangs

Konfigurieren Sie Microsoft Entra Authentifizierung und Tokenakquisition in der OWIN-Startklasse.

using Microsoft.Identity.Web;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        app.AddMicrosoftIdentityWebApp(
            Configuration,
            configSectionName: "AzureAd",
            openIdConnectScheme: "OpenIdConnect",
            cookieScheme: CookieAuthenticationDefaults.AuthenticationType,
            subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true);

        app.EnableTokenAcquisitionToCallDownstreamApi();
        app.AddDistributedTokenCaches();
    }
}

3. Aufrufen der downstream-API

Rufen Sie ein Token ab und rufen Sie die Downstream-API von einem MVC-Controller auf.

using Microsoft.Identity.Web;
using System.Threading.Tasks;
using System.Web.Mvc;

[Authorize]
public class ProfileController : Controller
{
    public async Task<ActionResult> Index()
    {
        var downstreamApi = TokenAcquirerFactory.GetDefaultInstance()
            .GetTokenAcquirer()
            .GetDownstreamApi();

        var userData = await downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");

        return View(userData);
    }
}

Note: OWIN-Unterstützung unterscheidet sich von ASP.NET Core. Ausführliche Informationen finden Sie in der OWIN-Dokumentation .


Befolgen bewährter Sicherheitsmethoden

Verwalten von Bereichen

Wenden Sie das Prinzip der geringsten Berechtigungen beim Anfordern von API-Berechtigungen an.

Zu tun:

  • Fordern Sie nur Bereiche an, die Sie benötigen.
  • Verwenden Sie die inkrementelle Zustimmung für erweiterte Funktionen
  • Dokumentieren Sie erforderliche Berechtigungen in Ihrer App

Nicht:

  • Anfordern unnötiger Bereiche im Voraus
  • Anfordern von Bereichen, die nur für Administratoren vorbehalten sind, ohne Angabe von Gründen.
  • Gehen Sie davon aus, dass alle Berechtigungen gewährt werden.

Sicherer Umgang mit Tokens

Befolgen Sie diese Richtlinien, um Zugriffstoken in Ihrer Anwendung zu schützen.

Zu tun:

  • Lassen Sie Microsoft. Identity.Web manage tokens
  • Verwenden des verteilten Caches in der Produktion
  • Sorgfältiger Umgang mit Tokenbeschaffungsfehlern

Nicht:

  • Token selbst speichern
  • Protokollieren von Zugriffstoken
  • Tokens an den clientseitigen Code senden

Fehler behandeln

Implementieren Sie eine robuste Fehlerbehandlung für Authentifizierungs- und API-Aufruffehler.

Zu tun:

  • Abfangen und Verarbeiten von Zustimmungsausnahmen
  • Bereitstellen klarer Fehlermeldungen für Benutzer
  • Protokollfehler für das Debuggen

Nicht:

  • Anzeigen von Tokenfehlern für die Benutzer
  • API-Aufrufe geräuschlos scheitern
  • Authentifizierungs-Ausnahmen ignorieren

Häufige Probleme beheben

Überprüfen Sie diese Lösungen auf häufig auftretende Authentifizierungsfehler.

Problem: "AADSTS65001: Der Benutzer oder Administrator hat nicht zugestimmt"

Ursache: Der Benutzer hat nicht den erforderlichen Bereichen zugestimmt.

Lösung:

catch (MicrosoftIdentityWebChallengeUserException ex)
{
    // Redirect to consent page
    return Challenge(
        new AuthenticationProperties { RedirectUri = Request.Path },
        OpenIdConnectDefaults.AuthenticationScheme);
}

Problem: "AADSTS50076: Mehrstufige Authentifizierung erforderlich"

Ursache: Der Benutzer muss MFA abschließen.

Lösung:

catch (MsalUiRequiredException)
{
    // Redirect user to sign in with MFA
    return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
}

Problem: Token bleiben nicht über App-Neustarts hinweg bestehen

Ursache: Verwenden des Speichercaches.

Solution: Wechseln zum verteilten Cache (Redis, SQL Server oder Cosmos DB).

Problem: 401 Nicht autorisiert von der downstream-API

Mögliche Ursachen:

  • Falsche Bereiche angefordert
  • API-Berechtigung wurde in der App-Registrierung nicht erteilt
  • Token abgelaufen

Lösung:

  1. Überprüfen Sie, ob die Scopes in der appsettings.json den API-Anforderungen entsprechen.
  2. Überprüfen der App-Registrierung auf API-Berechtigungen
  3. Sicherstellen, dass Token zwischengespeichert und aktualisiert werden

Detaillierte Diagnosen: Siehe das Protokollierungs- und Diagnosehandbuch für Korrelations-IDs, Token-Cache-Debugging und umfassende Problemlösungsmuster.


Optimieren der Leistung

Strategie für Token-Caching planen

Wählen Sie eine Zwischenspeicherungsstrategie aus, die Ihrer Bereitstellungstopologie entspricht.

  • Verwenden des verteilten Caches für Bereitstellungen mit mehreren Servern
  • Konfigurieren der entsprechenden Cache-Ablaufzeit
  • Überwachen der Cacheleistung

Minimieren von Tokenanforderungen

Microsoft.Identity.Web zwischenspeichert Token automatisch. Beide Aufrufe im folgenden Beispiel verwenden dasselbe zwischengespeicherte Token wieder.

// Bad: Multiple token acquisitions
var profile = await _downstreamApi.GetForUserAsync<Profile>(
    "API",
    options => options.RelativePath = "profile");
var settings = await _downstreamApi.GetForUserAsync<Settings>(
    "API",
    options => options.RelativePath = "settings");

// Good: Single token, multiple calls (token is cached)
// Both calls use the same cached token
var profile = await _downstreamApi.GetForUserAsync<Profile>(
    "API",
    options => options.RelativePath = "profile");
var settings = await _downstreamApi.GetForUserAsync<Settings>(
    "API",
    options => options.RelativePath = "settings");

Parallele API-Aufrufe durchführen

Rufen Sie mehrere nachgeschaltete APIs gleichzeitig auf, um die Gesamtlatenz zu reduzieren.

// Call multiple APIs in parallel
var profileTask = _downstreamApi.GetForUserAsync<Profile>(
    "API1",
    options => options.RelativePath = "profile");
var settingsTask = _downstreamApi.GetForUserAsync<Settings>(
    "API2",
    options => options.RelativePath = "settings");

await Task.WhenAll(profileTask, settingsTask);

var profile = profileTask.Result;
var settings = settingsTask.Result;

Hier finden Sie weitere Anleitungen für verwandte Szenarien.