本指南說明如何以 Microsoft Entra ID 認證與授權保護 .NET Aspire分散式應用程式。 內容涵蓋:
-
Blazor Server 前端 (
MyService.Web):使用者使用 OpenID Connect 登入並取得令牌 -
Protected API 後端 (
MyService.ApiService):使用 Microsoft.Identity.Web 進行 JWT 驗證。 - 端對端流程:Blazor 取得存取權杖,並使用 Aspire 服務發現來呼叫被保護的 API
本指南假設你是從以下指令建立的 Aspire 專案開始的:
aspire new aspire-starter --name MyService
先決條件
- .NET 9 SDK 或更新版本
- .NET Aspire CLI - 參見 安裝 Aspire CLI
- Microsoft Entra 租戶 — 請參閱 在 Microsoft Entra ID 中註冊應用程式 以進行設定
小提示
剛加入 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
- 前往 Microsoft Entra 管理中心>身份識別>應用程式>應用程式註冊。
- 選取新增註冊。
-
名稱:
MyService.ApiService - 支援的帳號類型: 僅限此組織目錄中的帳號(單一租戶)
- 選取 註冊。
-
名稱:
- 請到 Application ID URI 旁邊的 Expose an API>Add 。
- 接受預設的(
api://<client-id>)或自訂它。 - 選擇 新增示波器:
-
範圍名稱:
access_as_user - 誰可以同意: 管理員與使用者
- 管理員同意顯示名稱: 存取 MyService 的 API
- 管理員同意說明: 允許應用程式代表登入使用者存取 MyService API。
- 選取新增範圍。
-
範圍名稱:
- 接受預設的(
- 複製 應用程式(客戶端)ID ——這兩個
appsettings.json檔案都需要。
欲了解更多資訊,請參閱 快速入門:設定應用程式以暴露網頁 API。
步驟 2:註冊網頁應用程式
- 請前往應用程式註冊>新註冊。
-
名稱:
MyService.Web - 支援的帳號類型: 僅限本組織目錄中的帳號
-
重定向 URI: 選擇 網頁 並輸入你應用程式的網址 +
/signin-oidc- 關於本地開發:
https://localhost:7001/signin-oidc(請檢查實際launchSettings.json埠)
- 關於本地開發:
- 選取 註冊。
-
名稱:
- 到認證>新增URI 以新增所有開發 URL(來源
launchSettings.json)。 - 移至 憑證和密碼>用戶端密碼>新增用戶端密碼。
- 加上描述和有效期限。
- 立即複製秘密值——它不會再顯示。
- 前往 API 權限>新增權限>我的 API。
- 選擇
MyService.ApiService。 - 選擇
access_as_user>新增權限。 - 選擇 授予[tenant]管理員同意 (或首次使用時會提示使用者)。
- 選擇
- 複製網頁應用程式的
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
- 使用者造訪 Blazor 應用程式 →未驗證→看到「登入」按鈕。
-
使用者選擇登入 → 將重定向至
/authentication/login→ OIDC 挑戰 → Microsoft Entra。 -
使用者登入 → Microsoft Entra 重定向到
/signin-oidc→ 已建立的 cookie。 -
使用者前往天氣頁面 → Blazor 呼叫
WeatherApiClient.GetAsync()。 -
MicrosoftIdentityMessageHandler攔截請求,從快取取得標記(或靜默刷新),並附加Authorization: Bearer <token>標頭。 - API 會接收請求 → Microsoft。Identity.Web 驗證 JWT →回傳資料。
- 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}";
}
}
}
}
這個圖案的運作方式如下:
-
IsAuthenticatedAsync()在呼叫 API 前會檢查使用者是否已登入。 -
HandleExceptionAsync()捕捉MicrosoftIdentityWebChallengeUserException(或作為 InnerException)。 - 如果是挑戰例外狀況,系統會重新導向使用者依據所需的宣告或範圍進行重新驗證。
- 如果不是挑戰例外,
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
測試認證流程
- 開啟瀏覽器→ Blazor Web UI(請查看 Aspire 儀表板的網址)。
- 選擇 Login → 使用 Microsoft Entra 登入。
- 前往 天氣 頁面。
- 驗證天氣資料載入(來自受保護的 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」。