Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här artikeln visar hur du anropar underordnade API:er från ASP.NET Core och OWIN-webb-API:er med hjälp av Microsoft. Identity.Web. Artikeln fokuserar på On-Behalf-Of (OBO) flödet, där ditt API tar emot en token från en klient och utbyter den mot en ny token för att anropa ett annat API.
Förstå flödet för på-uppdrag-av
Med OBO-flödet (On-Behalf-Of) kan webb-API:et anropa underordnade API:er för den användare som anropade ditt API. Det här flödet underhåller användarens identitet och behörigheter i hela anropskedjan.
Granska OBO-flödesdiagrammet
Följande diagram visar hur OBO-flödet fungerar mellan ditt API, Microsoft Entra ID och det underordnade API:et.
sequenceDiagram
participant Client as Client App
participant YourAPI as Your Web API
participant AzureAD as Microsoft Entra ID
participant DownstreamAPI as Downstream API
Client->>YourAPI: 1. Call with access token
Note over YourAPI: Validate token
YourAPI->>AzureAD: 2. OBO request with user token
AzureAD->>AzureAD: 3. Validate & check consent
AzureAD->>YourAPI: 4. New access token for downstream API
Note over YourAPI: Cache token for user
YourAPI->>DownstreamAPI: 5. Call with new token
DownstreamAPI->>YourAPI: 6. Return data
YourAPI->>Client: 7. Return processed data
Förutsättningar för granskning
Kontrollera att följande finns på plats innan du börjar:
- Webb-API som konfigurerats med JWT Bearer-autentisering
- Appregistrering med API-behörigheter till underordnat API
- Klientappen måste ha behörighet att anropa ditt API
- Användaren måste ha samtyckt till både ditt API och underordnat API
Implementera i ASP.NET Core
Följande steg visar hur du konfigurerar ditt ASP.NET Core webb-API för att anropa underordnade API:er med hjälp av OBO-flödet.
1. Konfigurera autentisering
Konfigurera JWT Bearer-autentisering med explicit autentiseringsschema:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
// Add authentication with explicit scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
builder.Services.AddAuthorization();
builder.Services.AddControllers();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
2. Konfigurera appsettings.json
Lägg till din Microsoft Entra app-registreringsinformation och API-konfigurationen i appsettings.json.
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-api-client-id",
"ClientCredentials": [
{
"SourceType": "ClientSecret",
"ClientSecret": "your-client-secret"
}
],
"Audience": "api://your-api-client-id"
},
"DownstreamApis": {
"GraphAPI": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": ["https://graph.microsoft.com/.default"]
},
"PartnerAPI": {
"BaseUrl": "https://partnerapi.example.com",
"Scopes": ["api://partner-api-id/read"]
}
}
}
3. Lägg till underordnat API-stöd
Registrera underordnade API:er från konfigurationsavsnittet.
using Microsoft.Identity.Web;
builder.Services.AddDownstreamApis(
builder.Configuration.GetSection("DownstreamApis"));
4. Anropa underordnat API från ditt API
Mata IDownstreamApi in i kontrollanten och använd den för att anropa underordnade API:er för användarens räkning.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
private readonly IDownstreamApi _downstreamApi;
private readonly ILogger<DataController> _logger;
public DataController(
IDownstreamApi downstreamApi,
ILogger<DataController> logger)
{
_downstreamApi = downstreamApi;
_logger = logger;
}
[HttpGet("userdata")]
public async Task<ActionResult<UserData>> GetUserData()
{
try
{
// Call downstream API using OBO flow
// Token from incoming request is automatically used
var userData = await _downstreamApi.GetForUserAsync<UserData>(
"PartnerAPI",
"api/users/me");
return Ok(userData);
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
// User needs to consent to downstream API permissions
_logger.LogWarning(ex, "User consent required for downstream API");
return Unauthorized(new { error = "consent_required", scopes = ex.Scopes });
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Downstream API call failed");
return StatusCode(500, "Failed to retrieve data from downstream service");
}
}
[HttpPost("process")]
public async Task<ActionResult<ProcessResult>> ProcessData([FromBody] DataRequest request)
{
// Call downstream API with POST
var result = await _downstreamApi.PostForUserAsync<DataRequest, ProcessResult>(
"PartnerAPI",
"api/process",
request);
return Ok(result);
}
}
Konfigurera cachelagring av token
Välj en strategi för tokencache baserat på distributionsmiljön.
Använda minnesintern cache för utveckling
Följande kod lägger till en minnesintern tokencache, som endast är lämplig för utveckling.
builder.Services.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
Varning! Använd distribuerad cache för produktion.
Använda distribuerad cache för produktion
För produktions-API:er med flera instanser använder du distribuerad cachelagring:
using Microsoft.Extensions.Caching.StackExchangeRedis;
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "MyWebApi";
});
builder.Services.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDistributedTokenCaches();
Konfigurera andra distribuerade cacheprovidrar
Du kan också använda SQL Server, Cosmos DB eller PostgreSQL som distribuerad cacheprovider.
// SQL Server
builder.Services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("TokenCacheDb");
options.SchemaName = "dbo";
options.TableName = "TokenCache";
});
// Cosmos DB
builder.Services.AddCosmosDbTokenCaches(options =>
{
options.DatabaseId = "TokenCache";
options.ContainerId = "Tokens";
});
// PostgreSQL (requires Microsoft.Extensions.Caching.Postgres)
builder.Services.AddDistributedPostgresCache(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("PostgresCache");
options.SchemaName = builder.Configuration["PostgresCache:SchemaName"];
options.TableName = builder.Configuration["PostgresCache:TableName"];
options.CreateIfNotExists = builder.Configuration.GetValue<bool>("PostgresCache:CreateIfNotExists");
});
Hantera långvariga processer med OBO
För långvariga bakgrundsprocesser behöver du särskild hantering eftersom användarens token kan upphöra att gälla.
Förstå förfalloutmaningen för token
Följande diagram visar hur förfallodatum för token kan påverka tidskrävande processer.
graph TD
A[Client calls API] --> B[API receives user token]
B --> C[API starts long process]
C --> D{Token expires?}
D -->|Yes| E[ OBO fails]
D -->|No| F[ OBO succeeds]
style E fill:#f8d7da
style F fill:#d4edda
Välj strategier för sessionsnycklar
Långvariga OBO-processer använder en sessionsnyckel för att associera en cachelagrad OBO-token med ett visst bakgrundsarbetsflöde. Det finns två alternativ:
| Tillvägagångssätt | När det bör användas |
|---|---|
Explicit nyckel – du anger din egen nyckel (till exempel en Guid) |
Du har redan en naturlig identifierare för arbetsobjektet (process-ID, jobb-ID osv.) |
AllocateForMe – tokenskiktet genererar automatiskt en nyckel |
Du har ingen naturlig identifierare, eller så vill du att identitetsplattformen ska hantera nyckel unikhet. SDK använder hash(client_token) internt |
Implementera långvariga processer med en explicit nyckel
I följande exempel visas hur du använder en explicit nyckel, till exempel ett process-ID, för långvariga bakgrundsarbetsflöden.
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class ProcessingController : ControllerBase
{
private readonly IDownstreamApi _downstreamApi;
private readonly IBackgroundTaskQueue _taskQueue;
public ProcessingController(
IDownstreamApi downstreamApi,
IBackgroundTaskQueue taskQueue)
{
_downstreamApi = downstreamApi;
_taskQueue = taskQueue;
}
[HttpPost("start")]
public async Task<ActionResult<ProcessStatus>> StartLongProcess([FromBody] ProcessRequest request)
{
var processId = Guid.NewGuid();
// Queue the long-running task
_taskQueue.QueueBackgroundWorkItem(async (cancellationToken) =>
{
await ProcessDataAsync(processId, request, cancellationToken);
});
return Accepted(new ProcessStatus
{
ProcessId = processId,
Status = "Started"
});
}
private async Task ProcessDataAsync(
Guid processId,
ProcessRequest request,
CancellationToken cancellationToken)
{
try
{
// The cached refresh token allows token acquisition even if original token expired
var data = await _downstreamApi.GetForUserAsync<ProcessData>(
"PartnerAPI",
options => {
options.RelativePath = "api/process/data";
options.AcquireTokenOptions.LongRunningWebApiSessionKey = processId.ToString()
},
cancellationToken: cancellationToken);
// Process data...
await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken);
// Call API again (token may need refresh)
await _downstreamApi.PostForUserAsync<ProcessData, ProcessResult>(
"PartnerAPI",
options => {
options.RelativePath = "api/process/complete";
options.AcquireTokenOptions.LongRunningWebApiSessionKey = processId.ToString()
},
data,
cancellationToken: cancellationToken);
}
catch (Exception ex)
{
// Log error and update process status
}
}
}
Implementera långvariga processer med AllocateForMe
I stället för att hantera din egen nyckel anger du LongRunningWebApiSessionKey till det särskilda sentinel-värdet AcquireTokenOptions.LongRunningWebApiSessionKeyAuto (strängen "AllocateForMe"). Vid det första anropet genererar anskaffningsskiktet för token automatiskt en unik sessionsnyckel och skriver tillbaka den till samma AcquireTokenOptions instans. Sedan läser du den genererade nyckeln och skickar den till alla efterföljande anrop.
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class AutoKeyProcessingController : ControllerBase
{
private readonly IDownstreamApi _downstreamApi;
private readonly IBackgroundTaskQueue _taskQueue;
public AutoKeyProcessingController(
IDownstreamApi downstreamApi,
IBackgroundTaskQueue taskQueue)
{
_downstreamApi = downstreamApi;
_taskQueue = taskQueue;
}
[HttpPost("start")]
public async Task<ActionResult<ProcessStatus>> StartLongProcess([FromBody] ProcessRequest request)
{
// First call: let the platform allocate a session key
var options = new DownstreamApiOptions
{
RelativePath = "api/process/data",
AcquireTokenOptions = new AcquireTokenOptions
{
// Sentinel value — the platform will replace this with a generated key
LongRunningWebApiSessionKey = AcquireTokenOptions.LongRunningWebApiSessionKeyAuto // "AllocateForMe"
}
};
var data = await _downstreamApi.GetForUserAsync<ProcessData>(
"PartnerAPI",
optionsOverride => {
optionsOverride.RelativePath = options.RelativePath;
optionsOverride.AcquireTokenOptions.LongRunningWebApiSessionKey =
options.AcquireTokenOptions.LongRunningWebApiSessionKey;
});
// After the call, the platform has replaced the sentinel with the generated key.
string generatedSessionKey = options.AcquireTokenOptions.LongRunningWebApiSessionKey;
// generatedSessionKey is now a unique string such as "a1b2c3d4..." — no longer "AllocateForMe".
// Queue background work using the generated key
_taskQueue.QueueBackgroundWorkItem(async (cancellationToken) =>
{
await ContinueProcessingAsync(generatedSessionKey, data, cancellationToken);
});
return Accepted(new ProcessStatus
{
SessionKey = generatedSessionKey,
Status = "Started"
});
}
private async Task ContinueProcessingAsync(
string sessionKey,
ProcessData data,
CancellationToken cancellationToken)
{
// Process data...
await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken);
// Subsequent calls: reuse the generated session key
await _downstreamApi.PostForUserAsync<ProcessData, ProcessResult>(
"PartnerAPI",
options => {
options.RelativePath = "api/process/complete";
options.AcquireTokenOptions.LongRunningWebApiSessionKey = sessionKey;
},
data,
cancellationToken: cancellationToken);
}
}
Granska viktiga överväganden
Tänk på följande när du implementerar långvariga OBO-processer.
- Livslängd för sessionsnyckel: Lagra den genererade sessionsnyckeln tillsammans med ditt arbetsobjekt (databas, kömeddelande osv.) så att bakgrundsarbetare kan hämta den.
- Tokencache: Använd distribuerad cache för bakgrundsprocesser.
-
Användarkontext: Bakgrundsarbetaren kan komma åt
HttpContext.User. - Felhantering: Token kan fortfarande upphöra att gälla om användaren återkallar medgivandet.
Hantera fel i API:er
Webb-API:er kräver specifika felhanteringsmönster eftersom de inte kan omdirigera användare till interaktiva medgivandeflöden.
Hantera MicrosoftIdentityWebChallengeUserException
I webb-API:er kan du inte omdirigera användare till medgivande. Returnera i stället ett korrekt felsvar:
[HttpGet("data")]
public async Task<ActionResult> GetData()
{
try
{
var data = await _downstreamApi.GetForUserAsync<Data>("PartnerAPI", "api/data");
return Ok(data);
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
// Return 401 with consent information
return Unauthorized(new
{
error = "consent_required",
error_description = "Additional user consent required",
scopes = ex.Scopes,
claims = ex.Claims
});
}
}
Hantera medgivandekrav i klientappar
Klientappen ska hantera 401-svaret och utlösarmedgivandet:
// Client app code
var response = await httpClient.GetAsync("https://yourapi.example.com/api/data");
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
var error = await response.Content.ReadFromJsonAsync<ConsentError>();
if (error?.error == "consent_required")
{
// Trigger incremental consent in client app
// This will redirect user to Microsoft Entra ID for consent
throw new MsalUiRequiredException(error.error_description, error.scopes);
}
}
Hantera underordnade API-fel
Mappa underordnade API-felsvar till lämpliga HTTP-statuskoder för dina anropare.
[HttpGet("data")]
public async Task<ActionResult> GetData()
{
try
{
var data = await _downstreamApi.GetForUserAsync<Data>("PartnerAPI", "api/data");
return Ok(data);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
return NotFound("Resource not found in downstream service");
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.BadRequest)
{
return BadRequest("Invalid request to downstream service");
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Downstream API returned {StatusCode}", ex.StatusCode);
return StatusCode(502, "Downstream service error");
}
}
Implementera i OWIN (.NET Framework)
Följande steg visar hur du konfigurerar ett OWIN-baserat webb-API för att anropa underordnade API:er.
1. Konfigurera Startup.cs
Konfigurera OWIN-mellanprogram med Microsoft. Identity.Web i klassen Startup.
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.OWIN;
using Owin;
public class Startup
{
public void Configuration(IAppBuilder app)
{
OwinTokenAcquirerFactory factory = TokenAcquirerFactory.GetDefaultInstance<OwinTokenAcquirerFactory>();
app.AddMicrosoftIdentityWebApi(factory);
factory.Services
.AddMicrosoftGraph()
.AddDownstreamApis(factory.Configuration.GetSection("DownstreamAPIs"));
factory.Build();
}
}
2. Anropa API från kontroller
Använd tilläggsmetoder på kontrollanten för att hämta Graph-klienten, underordnad API-hjälp eller auktoriseringshuvudprovider.
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using System.Web.Http;
[Authorize]
public class DataController : ApiController
{
private readonly IDownstreamApi _downstreamApi;
public DataController()
{
GraphServiceClient graphServiceClient = this.GetGraphServiceClient();
var me = await graphServiceClient.Me.Request().GetAsync();
// OR - Example calling a downstream directly with the IDownstreamApi helper (uses the
// authorization header provider, encapsulates MSAL.NET)
// downstreamApi won't be null if you added services.AddMicrosoftGraph()
// in the Startup.auth.cs
IDownstreamApi downstreamApi = this.GetDownstreamApi();
var result = await downstreamApi.CallApiForUserAsync("DownstreamAPI");
// OR - Get an authorization header (uses the token acquirer)
IAuthorizationHeaderProvider authorizationHeaderProvider =
this.GetAuthorizationHeaderProvider();
}
[HttpGet]
[Route("api/data")]
public async Task<IHttpActionResult> GetData()
{
var data = await _downstreamApi.GetForUserAsync<Data>(
"PartnerAPI",
options => options.RelativePath = "api/data",
options => options.Scopes = new[] { "api://partner/read" });
return Ok(data);
}
}
Anropa flera underordnade API:er
Ditt API kan anropa flera underordnade API:er i en enda begäran:
[HttpGet("dashboard")]
public async Task<ActionResult<Dashboard>> GetDashboard()
{
try
{
// Call multiple APIs in parallel
var userTask = _downstreamApi.GetForUserAsync<User>(
"GraphAPI", "me");
var dataTask = _downstreamApi.GetForUserAsync<Data>(
"PartnerAPI", "api/data");
var settingsTask = _downstreamApi.GetForUserAsync<Settings>(
"PartnerAPI", "api/settings");
await Task.WhenAll(userTask, dataTask, settingsTask);
return Ok(new Dashboard
{
User = userTask.Result,
Data = dataTask.Result,
Settings = settingsTask.Result
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to retrieve dashboard data");
return StatusCode(500, "Failed to retrieve dashboard");
}
}
Följ metodtipsen
Använd dessa rekommendationer för att förbättra tillförlitligheten och säkerheten för dina API-till-API-anrop.
Använda distribuerad cache i produktion
Undvik minnesinterna cacheminnen i produktionsdistributioner. I följande exempel jämförs de två metoderna.
// Bad: In-memory cache in production
.AddInMemoryTokenCaches();
// Good: Distributed cache in production
.AddDistributedTokenCaches();
Konfigurera loggning
Lägg till strukturerad loggning för att samla in autentiserings- och underordnade API-händelser.
builder.Services.AddLogging(config =>
{
config.AddConsole();
config.AddApplicationInsights();
config.SetMinimumLevel(LogLevel.Information);
});
Ange lämpliga tidsgränser
Konfigurera HTTP-klienttimeouter för att förhindra långa väntetider för underordnade tjänster som inte svarar.
builder.Services.AddDownstreamApi("PartnerAPI", options =>
{
options.BaseUrl = "https://partnerapi.example.com";
options.HttpClientName = "PartnerAPI";
});
builder.Services.AddHttpClient("PartnerAPI", client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
});
Verifiera inkommande token
Kontrollera att api:et verifierar token korrekt. Följande kod binder tokenverifieringsinställningar från konfigurationen.
builder.Services.AddMicrosoftIdentityWebApi(options =>
{
builder.Configuration.Bind("AzureAd", options);
});
Felsök vanliga fel
Använd de här lösningarna för att lösa vanliga problem med OBO-flödet.
Åtgärda "AADSTS50013: Signaturvalideringen för påståendet misslyckades"
Orsak: Klienthemligheten eller certifikatet är felkonfigurerat i API:ets appregistrering.
Solution: Kontrollera att klientautentiseringsuppgifterna i appsettings.json matchar Microsoft Entra ID appregistrering.
Lös "AADSTS65001: Användaren eller administratören har inte samtyckt"
Orsak: Användaren har inte samtyckt till att ditt API anropar det underordnade API:et.
Lösning: Returnera rätt fel till klientappen och utlösa medgivandeflödet i klienten.
Lös "AADSTS500133: Försäkran ligger inte inom dess giltiga tidsintervall"
Orsak: Klocksnedvridning mellan servrar eller en token som har upphört att gälla.
Lösning:
- Synkronisera serverklockor
- Kontrollera förfallodatum för token
- Kontrollera att tokencache fungerar korrekt
Lös OBO-token som inte cachelagrats
Orsak: Den distribuerade cachen är inte konfigurerad eller så finns det problem med cachenyckeln.
Lösning:
- Verifiera distribuerad cacheanslutning
- Kontrollera att
oidochtidpåståenden finns i inkommande token - Aktivera felsökningsloggning för att se cacheåtgärder
Lösa flera API-instanser som inte delar cache
Orsak: API:et använder minnesintern cache i stället för distribuerad cache.
Solution: Växla till distribuerad cache (Redis, SQL Server, Cosmos DB).
För detaljerad diagnostik: Se Loggnings- och diagnostikguiden för korrelations-ID:n, felsökning av tokencache, PII-loggningskonfiguration och omfattande felsökningsarbetsflöden.
Utforska relaterat innehåll
- Långvariga processer
- Token-cachelagring
- Ringa från webbappar
- Scenarier för webb-API
- API bakom gatewayer
- Loggning och diagnostik – Felsöka problem med autentisering och token
- Auktoriseringsguide – Validering av RequiredScope- och appbehörighet
- Anpassningsguide – Anpassning av avancerade tokenförvärv
Nästa steg: Lär dig mer om calling Microsoft Graph eller anpassade API:er med specialiserade integrationsmönster.