部署受保護的 API 於閘道器後方

部署使用 Microsoft.Identity.Web 保護的 ASP.NET Core 網路 API,這些 API 部署在 Azure API 閘道和反向代理後面,包括 Azure API 管理(APIM)、Azure Front Door 和 Azure 應用程式閘道。

了解閘道需求

當你將受保護的 API 部署在閘道後時,必須處理以下幾項考量:

  • 轉發標頭 - 保留原始請求上下文(方案、主機、IP)
  • 令牌驗證 - 確保受眾聲明與閘道網址相符
  • CORS 設定 - 正確處理跨來源請求
  • 健康端點 - 提供未經認證的健康檢查
  • 基於路徑的路由 - 支援閘道層級路徑前綴
  • SSL/TLS 終止 - 當閘道終止 SSL 時,正確處理 HTTPS

檢視常見閘道情境

根據你的需求選擇閘道器。 以下章節將介紹最常見的 Azure 閘道服務,針對受保護的 API。

Azure API 管理 (APIM)

使用情境: 企業 API 閘道器,具備政策、速率限制與轉換功能

架構:

Client → Microsoft Entra ID → Token
Client → APIM (apim.azure-api.net) → Backend API (app.azurewebsites.net)

主要考慮因素:

  • APIM 政策可以在轉發到後端前驗證 JWT 代幣
  • 後端 API 仍然會驗證 tokens
  • 受眾聲明必須與 APIM URL 或後端 URL 相符(請相應配置)

Azure Front Door

使用情境: 全球負載平衡、CDN、DDoS 防護

架構:

Client → Microsoft Entra ID → Token
Client → Front Door (azurefd.net) → Backend API (regional endpoints)

主要考慮因素:

  • Front Door 轉發附有X-Forwarded-* 標頭的請求
  • 在前門終止 SSL/TLS
  • 憑證受眾驗證需要設定

Azure 應用程式閘道

使用情境: 區域負載平衡、WAF、基於路徑的路由

架構:

Client → Microsoft Entra ID → Token
Client → Application Gateway → Backend API (multiple instances)

主要考慮因素:

  • 網路應用防火牆(WAF)整合
  • 基於路徑的路由規則
  • 後端健康探測器需要未認證的端點

配置常見模式

套用這些設定模式,確保你的受保護 API 在任何閘道器後都能正常運作。

1. 轉接標頭中介軟體

在閘道器後面時,務必設定轉發標頭中介軟體。 以下程式碼註冊中介軟體,並設定其在驗證前執行:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

// Configure forwarded headers BEFORE authentication
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
                                ForwardedHeaders.XForwardedProto |
                                ForwardedHeaders.XForwardedHost;

    // Clear known networks/proxies to accept forwarded headers from any source
    // (Azure infrastructure will be the proxy)
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();

    // Limit to specific headers if needed
    options.ForwardedForHeaderName = "X-Forwarded-For";
    options.ForwardedProtoHeaderName = "X-Forwarded-Proto";
    options.ForwardedHostHeaderName = "X-Forwarded-Host";
});

// Add authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

var app = builder.Build();

// USE forwarded headers BEFORE authentication middleware
app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();

app.Run();

轉發標頭中介軟體至關重要,因為它:

  • 保留原始用戶端 IP 位址以便記錄
  • 確保HttpContext.Request.Scheme反映了原始的 HTTPS 協議
  • 提供正確的 Host 標頭以用於重定向 URL 及令牌驗證

2. 代幣受眾配置

選項A:同時接受閘道和後端的 URL

在您的 appsettings.json 配置中加入多個有效的受眾:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "Audience": "api://your-client-id",
    "TokenValidationParameters": {
      "ValidAudiences": [
        "api://your-client-id",
        "https://your-backend.azurewebsites.net",
        "https://your-apim.azure-api.net"
      ]
    }
  }
}

或者,您也可以在以下模式 Program.cs中程式化配置多個受眾:

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

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

// Customize token validation to accept multiple audiences
builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
    var existingValidation = options.TokenValidationParameters.AudienceValidator;

    options.TokenValidationParameters.AudienceValidator = (audiences, token, parameters) =>
    {
        var validAudiences = new[]
        {
            "api://your-client-id",
            "https://your-backend.azurewebsites.net",
            "https://your-apim.azure-api.net",
            builder.Configuration["AzureAd:ClientId"] // Also accept ClientId
        };

        return audiences.Any(a => validAudiences.Contains(a, StringComparer.OrdinalIgnoreCase));
    };
});

選項 B:在 APIM 政策中重寫受眾

設定 APIM 在轉發到後端前驗證受眾聲明:

<policies>
    <inbound>
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401">
            <openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
            <audiences>
                <audience>api://your-client-id</audience>
            </audiences>
        </validate-jwt>

        <!-- Optionally modify token claims for backend -->
        <set-header name="X-Gateway-Validated" exists-action="override">
            <value>true</value>
        </set-header>
    </inbound>
</policies>

3. 健康端點配置

閘道器需要未經認證的健康端點來偵測探測。 在認證中介軟體前配置一個健康端點,以繞過令牌驗證:

var app = builder.Build();

// Health endpoint BEFORE authentication middleware
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }))
    .AllowAnonymous();

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

// Protected endpoints require authentication
app.MapControllers();

app.Run();

或者,您也可以使用內建的 ASP.NET Core 健康檢查框架,以獲得更豐富的健康報告:

using Microsoft.Extensions.Diagnostics.HealthChecks;

builder.Services.AddHealthChecks()
    .AddCheck("api", () => HealthCheckResult.Healthy());

var app = builder.Build();

app.MapHealthChecks("/health").AllowAnonymous();
app.MapHealthChecks("/ready").AllowAnonymous();

app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

4. 閘道器背後的 CORS 配置

當你使用 Azure Front Door 或 APIM 搭配前端應用程式時,請設定 CORS 允許來自閘道來源的請求:

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowGateway", policy =>
    {
        policy.WithOrigins(
            "https://your-apim.azure-api.net",
            "https://your-frontend.azurefd.net",
            "https://your-app.azurewebsites.net"
        )
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials(); // If using cookies
    });
});

var app = builder.Build();

app.UseForwardedHeaders();
app.UseCors("AllowGateway");
app.UseAuthentication();
app.UseAuthorization();

app.Run();

重要

CORS 必須在轉發標頭 之後 、認證 設定。


與 Azure API 管理整合

本節提供部署 Azure API 管理 後保護 API 的完整設定。

設定後端 API

Program.cs 中設定轉發標頭並設定 Microsoft Entra ID 驗證:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Forwarded headers for APIM
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.All;
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddControllers();

var app = builder.Build();

// Middleware order matters
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

將 Microsoft Entra 的配置加入至 appsettings.json

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-backend-api-client-id",
    "Audience": "api://your-backend-api-client-id"
  }
}

新增 APIM 入站策略以進行 JWT 驗證

定義一個入站政策,驗證 JWT 標記、套用速率限制,並將請求轉發至後端:

<policies>
    <inbound>
        <base />

        <!-- Validate JWT token -->
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
            <openid-config url="https://login.microsoftonline.com/{your-tenant-id}/v2.0/.well-known/openid-configuration" />
            <audiences>
                <audience>api://your-backend-api-client-id</audience>
            </audiences>
            <issuers>
                <issuer>https://login.microsoftonline.com/{your-tenant-id}/v2.0</issuer>
            </issuers>
            <required-claims>
                <claim name="scp" match="any">
                    <value>access_as_user</value>
                </claim>
            </required-claims>
        </validate-jwt>

        <!-- Rate limiting -->
        <rate-limit calls="100" renewal-period="60" />

        <!-- Forward original host header -->
        <set-header name="X-Forwarded-Host" exists-action="override">
            <value>@(context.Request.OriginalUrl.Host)</value>
        </set-header>

        <!-- Forward to backend -->
        <set-backend-service base-url="https://your-backend.azurewebsites.net" />
    </inbound>

    <backend>
        <base />
    </backend>

    <outbound>
        <base />
    </outbound>

    <on-error>
        <base />
    </on-error>
</policies>

設定 APIM API 設定

請使用以下命名值與 API 設定來完成 APIM 設定:

命名值(用於重用性):

  • tenant-id:你的 Microsoft Entra 租戶識別碼
  • backend-api-client-id: 後端 API 的客戶端 ID
  • backend-base-urlhttps://your-backend.azurewebsites.net

API 設定:

  • API URL 後綴/api (可選路徑前綴)
  • 網路服務網址:透過使用命名值的政策設定
  • 需訂閱:是的(增加另一層安全措施)

設定用戶端應用程式

客戶端應用程式會請求 後端 API 的權杖,而不是 APIM。 以下程式碼會取得一個令牌,並透過 APIM 端點呼叫 API:

// Client app requests token
var result = await app.AcquireTokenSilent(
    scopes: new[] { "api://your-backend-api-client-id/access_as_user" },
    account)
    .ExecuteAsync();

// Call APIM URL with token
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", result.AccessToken);

// Add APIM subscription key
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "your-subscription-key");

var response = await client.GetAsync("https://your-apim.azure-api.net/api/weatherforecast");

與 Azure Front Door 整合

在 Azure Front Door 背後配置你的受保護的 API 以進行全球分發。

設定後端 API

Program.cs 中為 Azure Front Door 設定轉發標頭:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

// Configure for Azure Front Door
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
                                ForwardedHeaders.XForwardedProto |
                                ForwardedHeaders.XForwardedHost;

    // Accept headers from any source (Azure Front Door)
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();

    // Front Door specific headers
    options.ForwardedForHeaderName = "X-Forwarded-For";
    options.ForwardedProtoHeaderName = "X-Forwarded-Proto";
});

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

var app = builder.Build();

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

app.MapControllers();

app.Run();

設定前門起點

請在 Azure 入口網站完成以下步驟以設定 Front Door 原點:

  1. 建立前門個人檔案
  2. 將起源群組加入你的後端 API 實例
  3. 將健康探針配置到 /health 端點
  4. 設定僅限 HTTPS 轉發
  5. 啟用 WAF 政策(可選)

健康探針設定:

  • 路徑/health
  • 通訊協定:HTTPS
  • 方法:GET
  • 中場休息:30秒

處理多個區域

當你在 Front Door 後面部署到多個區域時,請新增區域感知功能以進行日誌記錄與診斷:

// Add region awareness for logging/diagnostics
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

app.Use(async (context, next) =>
{
    // Log the actual client IP and region
    var clientIp = context.Connection.RemoteIpAddress?.ToString();
    var forwardedFor = context.Request.Headers["X-Forwarded-For"].ToString();
    var frontDoorId = context.Request.Headers["X-Azure-FDID"].ToString();

    // Add to logger scope or response headers
    context.Response.Headers.Add("X-Served-By-Region",
        builder.Configuration["Region"] ?? "unknown");

    await next();
});

使用 Front Door 來驗證代幣

如果客戶請求指向 Front Door URL 的權杖,請將其加入有效受眾列表:

builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.TokenValidationParameters.ValidAudiences = new[]
    {
        "api://your-backend-api-client-id",
        "https://your-frontend.azurefd.net", // Front Door URL
        builder.Configuration["AzureAd:ClientId"]
    };
});

與 Azure 應用閘道整合

在 Azure 應用程式閘道 上配置受 Web 應用程式防火牆(WAF)保護的 API。

設定後端 API

Program.cs中為應用閘道設定轉發標頭:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

// Application Gateway uses standard forwarded headers
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
                                ForwardedHeaders.XForwardedProto;
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddHealthChecks();

var app = builder.Build();

// Health endpoint for Application Gateway probes
app.MapHealthChecks("/health").AllowAnonymous();

app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

配置應用程式閘道設定

在 Azure 入口網站中設定以下後端、健康探測與 WAF 設定:

後端設定:

  • 協定:HTTPS(建議)或 HTTP
  • 端口:443 或 80
  • 覆寫後端路徑:無(除非必要)
  • 自訂探針:是的,指向 /health

健康探測:

  • 協定:HTTPS 或 HTTP
  • 主機:保留原始設定或自行指定
  • 路徑/health
  • 中場休息:30秒
  • 不健康的閾值:3

WAF 政策:

  • 啟用 Web 應用程式防火牆 (WAF) 及 OWASP 3.2 規則集
  • 重要事項:確保標頭中的 Authorization JWT 標記不會被阻塞
  • 你可能需要為 RequestHeaderNames 包含「授權」的項目建立 WAF 例外。

建立基於路徑的路由

使用基於路徑的路由規則時,請設定後端 API 以處理路徑前綴:

// Backend API should work regardless of path prefix
var app = builder.Build();

// Option 1: Use path base (if gateway adds prefix)
app.UsePathBase("/api/v1");

// Option 2: Configure routing explicitly
app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

應用閘道規則:

  • 路徑/api/v1/*
  • 後端目標:你的後端池
  • 後端設定:使用已設定的設定

排除常見問題

利用這些解決方案解決在閘道後部署受保護 API 時最常見的問題。

問題:部署在閘道器之後遇到401未授權問題

徵兆:

  • API 在本地運作,但會在閘道後回傳 401
  • 在 jwt.ms 上解碼時,令牌似乎是有效的

可能的原因:

  1. 觀眾聲稱不匹配

    # Check token audience
    # Decode token and verify 'aud' claim matches one of:
    # - api://your-client-id
    # - https://your-backend.azurewebsites.net
    # - https://your-gateway-url
    
  2. 缺少轉發標頭中介軟體

    // Ensure this is BEFORE authentication
    app.UseForwardedHeaders();
    app.UseAuthentication();
    
  3. HTTPS 重定向問題

    // If gateway terminates SSL, may need to disable or configure carefully
    if (!app.Environment.IsDevelopment())
    {
        app.UseHttpsRedirection();
    }
    

Solution:

  • 啟用除錯日誌以查看令牌驗證細節
  • 在代幣驗證中加入多個有效受眾
  • 確認 X-Forwarded-* 閘道器是否將標頭轉發

問題:健康探針失效

徵兆:

  • Gateway 將後端標記為不健康
  • 健康端點回傳401

Solution:

確保健康檢查端點在驗證中介軟體啟用前運行

// Ensure health endpoint is BEFORE authentication
app.MapHealthChecks("/health").AllowAnonymous();

// Alternative: Use custom middleware
app.Map("/health", healthApp =>
{
    healthApp.Run(async context =>
    {
        context.Response.StatusCode = 200;
        await context.Response.WriteAsync("healthy");
    });
});

app.UseAuthentication(); // Health endpoint bypasses this

問題:前門後方的 CORS 錯誤

徵兆:

  • 檢查前選項請求失敗
  • 瀏覽器主控台顯示 CORS 錯誤

Solution:

將您的 Front Door 和前端來源添加到 CORS 政策中:

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy.WithOrigins(
            "https://your-frontend.azurefd.net",
            "https://your-app.com"
        )
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials();
    });
});

var app = builder.Build();

app.UseForwardedHeaders();
app.UseCors(); // Before authentication
app.UseAuthentication();
app.UseAuthorization();

問題:「轉送標頭」警告出現在日誌中

徵兆:

Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware: Unknown proxy

Solution:

清除已知網路和代理,以接受來自 Azure 基礎設施轉發的標頭:

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    // Clear known networks to accept from any proxy
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();

    // Or explicitly add Azure IP ranges (more secure but complex)
    // options.KnownProxies.Add(IPAddress.Parse("20.x.x.x"));
});

問題:APIM 回傳 401,但後端回傳 200

徵兆:

  • Token 對後端有效
  • APIM validate-jwt 政策失敗

Solution:

確認 APIM 政策受眾與代幣受眾相符:

<validate-jwt header-name="Authorization">
    <openid-config url="https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration" />
    <audiences>
        <!-- Must match the 'aud' claim in your token -->
        <audience>api://your-backend-api-client-id</audience>
    </audiences>
</validate-jwt>

問題:多種認證方案相互衝突

徵兆:

  • 同時使用 JWT Bearer 和其他身份驗證方案
  • 選錯方案

Solution:

在控制器中明確指定認證方案:

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

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .AddScheme<MyCustomOptions, MyCustomHandler>("CustomScheme", options => {});

// In controller, specify scheme explicitly
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class WeatherForecastController : ControllerBase
{
    // ...
}

遵循最佳做法

應用這些做法,在閘道器背後建立安全且具韌性的 API 部署。

1. 縱深防禦

即使閘道器已驗證,也務必在後端 API 中驗證憑證:

// Gateway validates token (APIM policy)
// Backend ALSO validates token (Microsoft.Identity.Web)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

閘道設定可以更改,代幣也可以重現。 縱深防禦對安全至關重要。

2. 使用受管理身份進行閘道到後端通訊

如果你的閘道器用自己的身份呼叫後端,請設定後端同時接受使用者權杖和管理身份權杖:

// Backend accepts both user tokens and gateway's managed identity
builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.TokenValidationParameters.ValidAudiences = new[]
    {
        "api://backend-api-client-id", // User tokens
        "https://management.azure.com" // Managed identity tokens (if applicable)
    };
});

3. 監控閘道指標

追蹤以下關鍵指標,以維持對閘道部署的可視性:

  • 401/403 錯誤率
  • 令牌驗證失敗
  • 健康探針故障
  • 轉發標頭(用於偵錯)

4. 運用應用洞察

新增 Application Insights 遙測以記錄閘道特定請求屬性:

builder.Services.AddApplicationInsightsTelemetry();

// Log custom properties
app.Use(async (context, next) =>
{
    var telemetry = context.RequestServices.GetRequiredService<TelemetryClient>();
    telemetry.TrackEvent("ApiRequest", new Dictionary<string, string>
    {
        ["ForwardedFor"] = context.Request.Headers["X-Forwarded-For"],
        ["OriginalHost"] = context.Request.Headers["X-Forwarded-Host"],
        ["Gateway"] = "APIM" // or "FrontDoor", "AppGateway"
    });

    await next();
});

5. 將健康與準備狀態分開

在現場狀態(服務是否正在運行?)和準備狀態(服務能否接受流量?)檢查時,使用不同的端點:

// Health: Is the service running?
app.MapGet("/health", () => Results.Ok()).AllowAnonymous();

// Ready: Can the service accept traffic?
app.MapHealthChecks("/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
}).AllowAnonymous();

builder.Services.AddHealthChecks()
    .AddCheck("database", () => /* check DB */ , tags: new[] { "ready" })
    .AddCheck("cache", () => /* check cache */ , tags: new[] { "ready" });

6. 記錄您的閘道設定

建立一個 README 或維基頁面,記錄以下內容:

  • 哪些閘道器正在使用中
  • 象徵性觀眾期待
  • CORS 設定
  • 健康探測器端點
  • 轉發標頭配置
  • 緊急回復程式

使用 Azure API 管理建立完整範例

本節提供完整且可生產環境的範例,展示 Azure API 管理 背後的 ASP.NET Core API 與 Microsoft Entra ID 認證。

後端 API(ASP.NET Core)

以下 Program.cs 配置轉發標頭、Microsoft Entra 身份驗證、健康檢查,以及應用程式洞察:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Forwarded headers for APIM
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.All;
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraph()
    .AddInMemoryTokenCaches();

// Application Insights
builder.Services.AddApplicationInsightsTelemetry();

// Health checks
builder.Services.AddHealthChecks();

builder.Services.AddControllers();

var app = builder.Build();

// Health endpoint (unauthenticated)
app.MapHealthChecks("/health").AllowAnonymous();

// Middleware order is critical
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

將以下 Microsoft Entra 與 Application Insights 設定加入 appsettings.json

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "backend-api-client-id",
    "Audience": "api://backend-api-client-id"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.Identity.Web": "Debug"
    }
  },
  "ApplicationInsights": {
    "ConnectionString": "your-connection-string"
  }
}

以下控制器需要進行認證,並記錄已轉發的標頭以供除錯:

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

[Authorize]
[ApiController]
[Route("[controller]")]
[RequiredScope("access_as_user")]
public class WeatherForecastController : ControllerBase
{
    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IActionResult Get()
    {
        // Log forwarded headers for debugging
        var forwardedFor = HttpContext.Request.Headers["X-Forwarded-For"];
        var forwardedHost = HttpContext.Request.Headers["X-Forwarded-Host"];

        _logger.LogInformation(
            "Request from {ForwardedFor} via {ForwardedHost}",
            forwardedFor,
            forwardedHost);

        return Ok(new[] { "Weather", "Forecast", "Data" });
    }
}

APIM 配置

以下入站政策驗證 JWT 代幣、套用速率限制、轉發標頭及配置 CORS:

<policies>
    <inbound>
        <base />

        <!-- Rate limiting per subscription -->
        <rate-limit-by-key calls="100" renewal-period="60"
                           counter-key="@(context.Subscription.Id)" />

        <!-- Validate JWT -->
        <validate-jwt header-name="Authorization"
                      failed-validation-httpcode="401"
                      failed-validation-error-message="Unauthorized">
            <openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
            <audiences>
                <audience>api://backend-api-client-id</audience>
            </audiences>
            <issuers>
                <issuer>https://login.microsoftonline.com/{tenant-id}/v2.0</issuer>
            </issuers>
            <required-claims>
                <claim name="scp" match="any">
                    <value>access_as_user</value>
                </claim>
            </required-claims>
        </validate-jwt>

        <!-- Forward headers -->
        <set-header name="X-Forwarded-Host" exists-action="override">
            <value>@(context.Request.OriginalUrl.Host)</value>
        </set-header>
        <set-header name="X-Forwarded-Proto" exists-action="override">
            <value>@(context.Request.OriginalUrl.Scheme)</value>
        </set-header>

        <!-- Backend URL -->
        <set-backend-service base-url="https://your-backend.azurewebsites.net" />
    </inbound>

    <backend>
        <base />
    </backend>

    <outbound>
        <base />

        <!-- Add CORS headers if needed -->
        <cors>
            <allowed-origins>
                <origin>https://your-frontend.com</origin>
            </allowed-origins>
            <allowed-methods>
                <method>GET</method>
                <method>POST</method>
            </allowed-methods>
            <allowed-headers>
                <header>*</header>
            </allowed-headers>
        </cors>
    </outbound>

    <on-error>
        <base />
    </on-error>
</policies>