將 Microsoft Entra ID 認證加到 .NET Aspire 應用程式

本指南說明如何以 Microsoft Entra ID 認證與授權保護 .NET Aspire分散式應用程式。 內容涵蓋:

  1. Blazor Server 前端 (MyService.Web):使用者使用 OpenID Connect 登入並取得令牌
  2. Protected API 後端 (MyService.ApiService):使用 Microsoft.Identity.Web 進行 JWT 驗證。
  3. 端對端流程:Blazor 取得存取權杖,並使用 Aspire 服務發現來呼叫被保護的 API

本指南假設你是從以下指令建立的 Aspire 專案開始的:

aspire new aspire-starter --name MyService

先決條件

小提示

剛加入 Aspire 嗎? 參見 .NET Aspire 概述。

了解兩階段工作流程

本指南採用兩階段方法:

階段 會發生什麼事 Result
第一階段 新增帶有佔位值的驗證代碼 App 能建置但無法運行
第二階段 配置 Microsoft Entra 應用程式註冊 應用程式以真實認證運行

在 Microsoft Entra ID 中註冊應用程式

在你的應用程式能驗證使用者之前,你需要在 Microsoft Entra 中註冊兩個應用程式:

應用程式註冊 Purpose 金鑰配置
API (MyService.ApiService) 驗證收到的憑證 應用程式識別碼 URI, access_as_user 範圍
網頁應用程式 (MyService.Web) 登入使用者,取得代幣 重導 URI、客戶端密鑰、API 權限

如果您已經設定好應用程式註冊,您需要這些數值以用於您的 appsettings.json。

  • TenantID — 您的Microsoft Entra租戶ID
  • API ClientID — 您的 API 應用程式註冊的應用程式(用戶端) ID
  • API 應用程式 ID URI — 通常 api://<api-client-id> (用於 Audiences 和 Scopes)
  • Web App ClientId — 您的網頁應用程式註冊的應用程式(用戶端) ID
  • 用戶端秘密 (或憑證)— 網頁應用程式的憑證(儲存在使用者秘密中,而非 appsettings.json)
  • 範圍 — 例如,你的網頁應用程式所請求的範圍, api://<api-client-id>/.default 或 api://<api-client-id>/access_as_user

步驟 1:註冊 API

  1. 前往 Microsoft Entra 管理中心>身份識別>應用程式>應用程式註冊。
  2. 選取新增註冊。
    • 名稱:MyService.ApiService
    • 支援的帳號類型: 僅限此組織目錄中的帳號(單一租戶)
    • 選取 註冊。
  3. 請到 Application ID URI 旁邊的 Expose an API>Add 。
    • 接受預設的(api://<client-id>)或自訂它。
    • 選擇 新增示波器:
      • 範圍名稱:access_as_user
      • 誰可以同意: 管理員與使用者
      • 管理員同意顯示名稱: 存取 MyService 的 API
      • 管理員同意說明: 允許應用程式代表登入使用者存取 MyService API。
      • 選取新增範圍。
  4. 複製 應用程式(客戶端)ID ——這兩個 appsettings.json 檔案都需要。

欲了解更多資訊,請參閱 快速入門:設定應用程式以暴露網頁 API。

步驟 2:註冊網頁應用程式

  1. 請前往應用程式註冊>新註冊。
    • 名稱:MyService.Web
    • 支援的帳號類型: 僅限本組織目錄中的帳號
    • 重定向 URI: 選擇 網頁 並輸入你應用程式的網址 + /signin-oidc
      • 關於本地開發:https://localhost:7001/signin-oidc (請檢查實際 launchSettings.json 埠)
    • 選取 註冊。
  2. 到認證>新增URI 以新增所有開發 URL(來源launchSettings.json)。
  3. 移至 憑證和密碼>用戶端密碼>新增用戶端密碼。
    • 加上描述和有效期限。
    • 立即複製秘密值——它不會再顯示。
  4. 前往 API 權限>新增權限>我的 API。
    • 選擇 MyService.ApiService。
    • 選擇 access_as_user>新增權限。
    • 選擇 授予[tenant]管理員同意 (或首次使用時會提示使用者)。
  5. 複製網頁應用程式的appsettings.json。

備註

有些組織不允許客戶秘密。 關於替代方案,請參見 憑證憑證 或 無憑證認證。

欲了解更多資訊,請參閱 快速入門:註冊申請。

步驟 3:更新設定

建立應用程式註冊後,更新你的 appsettings.json 檔案:

API(MyService.ApiService/appsettings.json):

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "YOUR_TENANT_ID",
    "ClientId": "YOUR_API_CLIENT_ID",
    "Audiences": ["api://YOUR_API_CLIENT_ID"]
  }
}

網頁應用程式(MyService.Web/appsettings.json):

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "YOUR_TENANT_ID",
    "ClientId": "YOUR_WEB_CLIENT_ID",
    "CallbackPath": "/signin-oidc",
    "ClientCredentials": [
      { "SourceType": "ClientSecret" }
    ]
  },
  "WeatherApi": {
    "Scopes": ["api://YOUR_API_CLIENT_ID/.default"]
  }
}

妥善保存秘密:

cd MyService.Web
dotnet user-secrets set "AzureAd:ClientCredentials:0:ClientSecret" "YOUR_SECRET_VALUE"
價值 在哪裡可以找到
TenantId Microsoft Entra 系統管理中心 > 概覽 > 租戶識別碼
API ClientId 應用程式註冊 > MyService.ApiService > 應用程式(用戶端)ID
Web ClientId 應用程式註冊 > MyService.Web > Application(client)ID
Client Secret 在步驟 2 建立(建立後立即複製)

備註

Aspire 起始範本會在WeatherApiClient專案中自動建立一個MyService.Web類別。 本指南中使用此型別 HttpClient 來示範呼叫受保護的 API。 你不需要自己創建這個類別——它是範本的一部分。


快速入門

本節提供簡明的參考資料,方便加入認證。 詳細攻略請參見 第一部分 和 第二部分。

API (MyService.ApiService)

安裝 Microsoft。Identity.Web NuGet 套件:

dotnet add package Microsoft.Identity.Web

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

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenant-id>",
    "ClientId": "<api-client-id>",
    "Audiences": ["api://<api-client-id>"]
  }
}

登錄認證與授權於 Program.cs:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization();
// ...
app.UseAuthentication();
app.UseAuthorization();
// ...
app.MapGet("/weatherforecast", () => { /* ... */ }).RequireAuthorization();

網頁應用程式(MyService.Web)

安裝 Microsoft。Identity.Web NuGet 套件:

dotnet add package Microsoft.Identity.Web

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

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenant-id>",
    "ClientId": "<web-client-id>",
    "CallbackPath": "/signin-oidc",
    "ClientCredentials": [{ "SourceType": "ClientSecret" }]
  },
  "WeatherApi": { "Scopes": ["api://<api-client-id>/.default"] }
}

在 Program.cs 中配置驗證、令牌擷取及下游 API 用戶端。

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

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<BlazorAuthenticationChallengeHandler>();

builder.Services.AddHttpClient<WeatherApiClient>(client =>
    client.BaseAddress = new("https+http://apiservice"))
    .AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));
// ...
app.UseAuthentication();
app.UseAuthorization();
app.MapGroup("/authentication").MapLoginAndLogout();

它 MicrosoftIdentityMessageHandler 會自動取得並附加代幣,並 BlazorAuthenticationChallengeHandler 處理同意與條件存取挑戰。

這很重要

別忘了在登入按鈕上創建 UserInfo.razor 。 詳情請參閱 新增 Blazor UI 元件 。

備註

BlazorAuthenticationChallengeHandler 和 LoginLogoutEndpointRouteBuilderExtensions 在 Microsoft.Identity.Web(v3.3.0+)中提供。 不需要複製檔案。


識別要修改的檔案

下表列出每個專案中你要更改的檔案:

專案 File Changes
ApiService Program.cs JWT 承載者認證,授權中介軟體
appsettings.json Microsoft Entra 配置
.csproj 加上 Microsoft.Identity.Web
網頁 Program.cs OIDC 認證、憑證取得、BlazorAuthenticationChallengeHandler
appsettings.json Microsoft Entra 配置,下游 API 範圍
.csproj 新增 Microsoft.Identity.Web(v3.3.0+)
Components/UserInfo.razor 登入按鈕介面(新檔案)
Components/Layout/MainLayout.razor 包含 UserInfo 元件
Components/Routes.razor 授權路由檢視(AuthorizeRouteView)用於受保護頁面
各頁面呼叫 API 用 ChallengeHandler 嘗試/接球

了解認證流程

下圖展示了 Blazor 前端、Microsoft Entra 與受保護 API 的互動方式:

flowchart LR
  A[User Browser] -->|1 Login OIDC| B[Blazor Server<br/>MyService.Web]
  B -->|2 Redirect| C[Microsoft Entra ID]
  C -->|3 auth code| B
  B -->|4 exchange auth code| C
  C -->|5 tokens| B
  B -->|6 cookie + session| A
  B -->|7 HTTP + Bearer token| D[ASP.NET API<br/>MyService.ApiService<br/>Microsoft.Identity.Web]
  D -->|8 Validate JWT| C
  D -->|9 Weather data| B
  1. 使用者造訪 Blazor 應用程式 →未驗證→看到「登入」按鈕。
  2. 使用者選擇登入 → 將重定向至 /authentication/login → OIDC 挑戰 → Microsoft Entra。
  3. 使用者登入 → Microsoft Entra 重定向到 /signin-oidc → 已建立的 cookie。
  4. 使用者前往天氣頁面 → Blazor 呼叫 WeatherApiClient.GetAsync()。
  5. MicrosoftIdentityMessageHandler 攔截請求,從快取取得標記(或靜默刷新),並附加 Authorization: Bearer <token> 標頭。
  6. API 會接收請求 → Microsoft。Identity.Web 驗證 JWT →回傳資料。
  7. Blazor 渲染天氣資料。

檢視解答結構

Aspire 入門範本會建立以下專案版面:

MyService/
├── MyService.AppHost/           # Aspire orchestration
├── MyService.ApiService/        # Protected API (Microsoft.Identity.Web)
├── MyService.Web/               # Blazor Server (Microsoft.Identity.Web)
├── MyService.ServiceDefaults/   # Shared defaults
└── MyService.Tests/             # Tests

第一部分:使用 Microsoft.Identity.Web 保護 API 後端。

本節配置 API 專案以驗證 Microsoft Entra 發行的 JWT 持有人憑證。

加入 Microsoft.Identity.Web 套件

執行以下指令安裝 Microsoft。Identity.Web NuGet 套件:

cd MyService.ApiService
dotnet add package Microsoft.Identity.Web

配置 Microsoft Entra 設定

將 Microsoft Entra 的配置加入至 MyService.ApiService/appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<your-tenant-id>",
    "ClientId": "<your-api-client-id>",
    "Audiences": [
      "api://<your-api-client-id>"
    ]
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

主要特性:

  • ClientId:Microsoft Entra API 應用程式註冊 ID
  • TenantId:您的 Microsoft Entra 租戶 ID,或多租戶使用 "organizations",或任何 Microsoft 帳戶使用 "common"
  • Audiences:有效的代幣受眾(通常是您的 App ID URI)

更新 API Program.cs

將 的內容 MyService.ApiService/Program.cs 替換為以下程式碼,以新增 JWT 承載認證並保護端點:

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

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Add Microsoft.Identity.Web JWT Bearer authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddProblemDetails();
builder.Services.AddOpenApi();
builder.Services.AddAuthorization();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

string[] summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild",
    "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];

app.MapGet("/", () =>
    "API service is running. Navigate to /weatherforecast to see sample data.");

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.RequireAuthorization();

app.MapDefaultEndpoints();
app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

主要變更:

  • 註冊 JWT 承載者認證 AddMicrosoftIdentityWebApi
  • 新增 app.UseAuthentication() 與 app.UseAuthorization() 中介軟體
  • 應用 .RequireAuthorization() 於受保護端點

測試受保護的 API

驗證 API 拒絕未經認證的請求並接受有效令牌。

發送無標記的請求:

curl https://localhost:<PORT>/weatherforecast
# Expected: 401 Unauthorized

請求時發送有效的令牌:

curl -H "Authorization: Bearer <TOKEN>" https://localhost:<PORT>/weatherforecast
# Expected: 200 OK with weather data

第二部分:設定 Blazor 前端以進行認證

Blazor Server 應用程式使用Microsoft.Identity.Web來:

  • 使用 OIDC 將使用者登入
  • 取得存取權杖以呼叫 API
  • 將標記附加到對外發出的 HTTP 請求

加入 Microsoft.Identity.Web 套件

執行以下指令安裝 Microsoft。Identity.Web NuGet 套件:

cd MyService.Web
dotnet add package Microsoft.Identity.Web

配置 Microsoft Entra 設定

將 Microsoft Entra 設定與下游 API 範圍加入 MyService.Web/appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<your-tenant>.onmicrosoft.com",
    "TenantId": "<tenant-guid>",
    "ClientId":  "<web-app-client-id>",
    "CallbackPath": "/signin-oidc",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "<your-client-secret>"
      }
    ]
  },
  "WeatherApi": {
    "Scopes": [ "api://<api-client-id>/.default" ]
  },
  "Logging": {
    "LogLevel":  {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

配置詳細資料:

  • ClientId: 網頁應用程式註冊 ID(非 API ID)
  • ClientCredentials:供網頁應用程式用來獲取代幣的憑證。 支援多種憑證類型。 請參閱 憑證總覽 以了解生產準備選項。
  • Scopes: 必須與 API 的 App ID URI /.default 並附後綴相符

警告

在生產環境方面,請使用憑證或管理身份,取代用戶端秘密。 建議的做法請參見 無憑證認證 。

更新網頁應用程式Program.cs

將 的內容 MyService.Web/Program.cs 替換為以下程式碼,以配置 OIDC 認證、憑證擷取及下游 API 用戶端:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using MyService.Web;
using MyService.Web.Components;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Authentication + Microsoft Identity Web
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddCascadingAuthenticationState();

// Blazor components
builder.Services.AddRazorComponents().AddInteractiveServerComponents();

// Blazor authentication challenge handler for incremental consent and Conditional Access
builder.Services.AddScoped<BlazorAuthenticationChallengeHandler>();

builder.Services.AddOutputCache();

// Downstream API client with MicrosoftIdentityMessageHandler
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
    // Aspire service discovery: resolves "apiservice" at runtime
    client.BaseAddress = new("https+http://apiservice");
})
.AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.UseOutputCache();

app.MapStaticAssets();
app.MapRazorComponents<App>()
   .AddInteractiveServerRenderMode();

// Login/Logout endpoints with incremental consent support
app.MapGroup("/authentication").MapLoginAndLogout();

app.MapDefaultEndpoints();
app.Run();

關鍵點:

  • AddMicrosoftIdentityWebApp: 配置 OIDC 認證
  • EnableTokenAcquisitionToCallDownstreamApi:啟用下游 API 的令牌獲取
  • AddScoped<BlazorAuthenticationChallengeHandler>: 處理 Blazor Server 中的增量同意與條件存取
  • AddMicrosoftIdentityMessageHandler: 自動將承載標記附加於 HttpClient 請求
  • https+http://apiservice:Aspire 服務發現會將其解析為實際的 API URL。
  • 中介軟體順序: UseAuthentication() → UseAuthorization() →端點

此 AddMicrosoftIdentityMessageHandler 擴充支援多種配置模式:

選項一:從 appsettings.json 設定(如前所述)

.AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));

選項二:內嵌配置與動作代理

.AddMicrosoftIdentityMessageHandler(options =>
{
    options.Scopes.Add("api://<api-client-id>/.default");
});

選項 3:逐請求設定(無參數)

.AddMicrosoftIdentityMessageHandler();

// Then in your service, configure per-request:
var request = new HttpRequestMessage(HttpMethod.Get, "/weatherforecast")
    .WithAuthenticationOptions(options =>
    {
        options.Scopes.Add("api://<api-client-id>/.default");
    });
var response = await _httpClient.SendAsync(request);

新增 Blazor UI 元件

這很重要

這個步驟常被忽略。 沒有 UserInfo 元件,使用者就無法登入。

BlazorAuthenticationChallengeHandler 和 LoginLogoutEndpointRouteBuilderExtensions 與 Microsoft.Identity.Web v3.3.0+ 一起發行。 只要你參考套件,這些檔案就會自動取得——不需要複製檔案。

創作 MyService.Web/Components/UserInfo.razor:

@using Microsoft.AspNetCore.Components.Authorization

<AuthorizeView>
    <Authorized>
        <span class="nav-item">Hello, @context.User.Identity?.Name</span>
        <form action="/authentication/logout" method="post" class="nav-item">
            <AntiforgeryToken />
            <input type="hidden" name="returnUrl" value="/" />
            <button type="submit" class="btn btn-link nav-link">Logout</button>
        </form>
    </Authorized>
    <NotAuthorized>
        <a href="/authentication/login?returnUrl=/" class="nav-link">Login</a>
    </NotAuthorized>
</AuthorizeView>

加入版面: 將 <UserInfo /> 包含至 MainLayout.razor 中:

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <UserInfo />
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

更新 Routes.razor 以支援 AuthorizeRouteView

在RouteView中將AuthorizeRouteView替換為Components/Routes.razor

@using Microsoft.AspNetCore.Components.Authorization

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
            <NotAuthorized>
                <p>You are not authorized to view this page.</p>
                <a href="/authentication/login">Login</a>
            </NotAuthorized>
        </AuthorizeRouteView>
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>

處理呼叫 API 頁面的例外

Blazor Server 需要對條件存取和同意進行明確的例外處理。 除非您的應用程式已取得預授權,並且您提前在MicrosoftIdentityWebChallengeUserException請求所有的範圍,否則您必須在每個呼叫下游 API 的頁面上處理Program.cs。

以下 Weather.razor 範例展示了正確的異常處理方法:

@page "/weather"
@attribute [Authorize]

@using Microsoft.AspNetCore.Authorization
@using Microsoft.Identity.Web

@inject WeatherApiClient WeatherApi
@inject BlazorAuthenticationChallengeHandler ChallengeHandler

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

@if (!string.IsNullOrEmpty(errorMessage))
{
    <div class="alert alert-warning">@errorMessage</div>
}
else if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;
    private string? errorMessage;

    protected override async Task OnInitializedAsync()
    {
        if (!await ChallengeHandler.IsAuthenticatedAsync())
        {
            await ChallengeHandler.ChallengeUserWithConfiguredScopesAsync("WeatherApi:Scopes");
            return;
        }

        try
        {
            forecasts = await WeatherApi.GetWeatherAsync();
        }
        catch (Exception ex)
        {
            // Handle incremental consent / Conditional Access
            if (!await ChallengeHandler.HandleExceptionAsync(ex))
            {
                errorMessage = $"Error loading weather data: {ex.Message}";
            }
        }
    }
}

這個圖案的運作方式如下:

  1. IsAuthenticatedAsync() 在呼叫 API 前會檢查使用者是否已登入。
  2. HandleExceptionAsync() 捕捉 MicrosoftIdentityWebChallengeUserException(或作為 InnerException)。
  3. 如果是挑戰例外狀況,系統會重新導向使用者依據所需的宣告或範圍進行重新驗證。
  4. 如果不是挑戰例外,HandleExceptionAsync 返回 false ,這樣你可以自行處理錯誤。

將客戶端秘密儲存在使用者秘密中

在開發過程中,請使用 .NET 秘密管理器安全地儲存用戶端秘密。

謹慎

不要將秘密添加至版本控制系統。

初始化使用者秘密並儲存用戶端秘密:

cd MyService.Web
dotnet user-secrets init
dotnet user-secrets set "AzureAd:ClientCredentials:0:ClientSecret" "<your-client-secret>"

接著更新 appsettings.json 移除硬編碼的秘密:

{
  "AzureAd": {
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret"
      }
    ]
  }
}

Microsoft。Identity.Web 支援多種憑證類型。 關於生產,請參見 憑證總覽。


驗證實作

請使用此清單確認您已完成所有必要步驟。

API 專案

  • [ ] 新增 Microsoft.Identity.Web 套件
  • [ ] 已更新 appsettings.json 和 AzureAd 部分
  • [ ] 更新 Program.cs 為 AddMicrosoftIdentityWebApi
  • 已將.RequireAuthorization()新增至受保護的端點

Web/Blazor 專案

  • [ ] 新增 Microsoft.Identity.Web 套件(v3.3.0+)
  • [ ] 已更新appsettings.json與AzureAdWeatherApi章節
  • [ ] 使用 OIDC 更新 Program.cs,取得令牌
  • [ ] 新增 AddScoped<BlazorAuthenticationChallengeHandler>()
  • [ ] 已建立 Components/UserInfo.razor (登入按鈕)
  • [ ] 已更新 MainLayout.razor ,包含以下內容 <UserInfo />
  • [ ] 更新 Routes.razor 為 AuthorizeRouteView
  • [ ] 在每個呼叫 API 的頁面上新增了 ChallengeHandler 的 try/catch 處理結構
  • [ ] 將用戶端秘密儲存在使用者秘密設定中

驗證

  • [ ] dotnet build 成功
  • [ ] 在 Microsoft Entra 管理中心創建的應用程式註冊
  • [ ] appsettings.json 有實際的 GUID(而非佔位符)

測試和疑難排解

完成實作後,執行應用程式並驗證端對端認證流程。

執行應用程式

啟動 Aspire AppHost,啟動網頁與 API 專案:

# From solution root
dotnet restore
dotnet build

# Launch AppHost (starts both Web and API)
dotnet run --project .\MyService.AppHost\MyService.AppHost.csproj

測試認證流程

  1. 開啟瀏覽器→ Blazor Web UI(請查看 Aspire 儀表板的網址)。
  2. 選擇 Login → 使用 Microsoft Entra 登入。
  3. 前往 天氣 頁面。
  4. 驗證天氣資料載入(來自受保護的 API)。

解決常見問題

下表列出常見問題及其解法:

Issue 解決方案
401 用於 API 呼叫 請確認 appsettings.json 中的範圍與 API 的 App ID URI 一致
OIDC 重定向失敗 在 Microsoft Entra 的重定向 URI 中加入 /signin-oidc
代幣未連接 確保 AddMicrosoftIdentityMessageHandler 在 HttpClient
服務發現失敗 查查 AppHost.cs 兩個專案的參考資料,並確保它們都在運行中
AADSTS65001 需管理員同意 — 在 Microsoft Entra 管理中心授予同意
沒有登入按鈕 確保 UserInfo.razor 存在並包含在 MainLayout.razor
同意循環 確保所有 API 呼叫頁面都包含 try/catch with HandleExceptionAsync

啟用 MSAL 日誌

在排除認證問題時,請啟用詳細的 MSAL 日誌以查看憑證取得細節。 將以下日誌級別添加到appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.Identity": "Debug",
      "Microsoft.IdentityModel": "Debug"
    }
  }
}

警告

在生產環境中關閉除錯日誌,因為它可能非常冗長。

檢查標記

要除錯令牌問題,請在 jwt.ms 解碼你的 JWT 並驗證:

  • aud (受眾):與你 API 的 Client ID 或 App ID URI 相符
  • iss (發行人):與您的租戶相符(https://login.microsoftonline.com/<tenant-id>/v2.0)
  • scp (範圍):包含所需的範圍
  • exp (過期):令牌尚未過期

探索常見情境

以下章節將說明如何擴展基礎實作以滿足更多使用情境。

保護 Blazor 頁面

將屬性 [Authorize] 加入需要驗證的頁面:

@page "/weather"
@attribute [Authorize]

或定義授權政策於Program.cs:

// Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
});
@attribute [Authorize(Policy = "AdminOnly")]

驗證 API 中的範圍

確保 API 只接受具有特定範圍的標記,方法是串連 RequireScope:

app.MapGet("/weatherforecast", () =>
{
    // ... implementation
})
.RequireAuthorization()
.RequireScope("access_as_user");

使用僅限應用程式的令牌(服務對服務)

對於背景程序情境或無使用者上下文的服務對服務呼叫,將 RequestAppToken 設為 true:

builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
    client.BaseAddress = new("https+http://apiservice");
})
.AddMicrosoftIdentityMessageHandler(options =>
{
    options.Scopes.Add("api://<api-client-id>/.default");
    options.RequestAppToken = true;
});

使用無憑證憑證進行生產

在 Azure 中進行生產部署時,請使用管理身份(managed identity)取代用戶端秘密。 請按以下方式設定 ClientCredentials 區段:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenant-guid>",
    "ClientId":  "<web-app-client-id>",
    "ClientCredentials": [
      {
        "SourceType": "SignedAssertionFromManagedIdentity",
        "ManagedIdentityClientId": "<user-assigned-mi-client-id>"
      }
    ]
  }
}

欲了解更多資訊,請參閱無憑證認證。

從 API 呼叫下游 API(代為呼叫)

如果您的 API 需要代表使用者呼叫另一個下游 API,請啟用代表者 token 取得功能:Program.cs

// MyService.ApiService/Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddDownstreamApi("GraphApi", builder.Configuration.GetSection("GraphApi"));

請將下游 API 的配置新增至:appsettings.json

{
  "GraphApi": {
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    "Scopes": [ "User.Read" ]
  }
}

接著從端點呼叫下游 API:

{
    var user = await downstreamApi.GetForUserAsync<JsonElement>("GraphApi", "me");
    return user;
}).RequireAuthorization();

更多資訊請參閱 「呼叫下游 API」。