このガイドでは、.NET AspireMicrosoft Entra ID 認証と承認を使用して分散アプリケーションをセキュリティで保護する方法について説明します。 以下が対象です。
-
Blazor Server フロントエンド (
MyService.Web): OpenID Connect とトークン取得を使用したユーザー サインイン -
Protected API バックエンド (
MyService.ApiService): Microsoft.Identity.Web を使用した JWT 検証 - エンド ツー エンド フロー: Blazor がアクセス トークンを取得し、「Aspire」サービス探索を用いて保護された API を呼び出す
このガイドでは、次のコマンドを使用して作成されたアスパイア プロジェクトを使い始めたものとします。
aspire new aspire-starter --name MyService
前提条件
- .NET 9 SDK 以降
.NET Aspire CLI -Install Aspire CLI - Microsoft Entra テナント — セットアップについては、 Microsoft Entra ID のアプリの登録に関するページを参照してください
ヒント
Aspireを初めて使用しますか? .NET Aspireの概要を参照してください。
2 フェーズ ワークフローを理解する
このガイドは、2 段階のアプローチに従います。
| Phase | 何が起きるか | 結果 |
|---|---|---|
| フェーズ 1 | プレースホルダー値を使用して認証コードを追加する | アプリがビルドされるが、実行できない |
| フェーズ 2 | Microsoft Entraアプリの登録をプロビジョニングする | 実際の認証を使用してアプリを実行する |
Microsoft Entra IDにアプリを登録する
アプリでユーザーを認証するには、Microsoft Entraに 2 つのアプリ登録が必要です。
| アプリの登録 | Purpose | キー構成 |
|---|---|---|
API (MyService.ApiService) |
受信トークンを検証します | アプリ ID URI、 access_as_user スコープ |
Web アプリ (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 — Web アプリ登録のアプリケーション (クライアント) ID
- クライアント シークレット (または証明書) - Web アプリの資格情報 (appsettings.jsonではなく、ユーザー シークレットに格納)
-
スコープ - Web アプリが要求するスコープ (たとえば、
api://<api-client-id>/.defaultやapi://<api-client-id>/access_as_user
手順 1: API を登録する
- Microsoft Entra 管理センター>Identity>Applications>アプリの登録 に移動します。
-
[新規登録] を選択します。
-
名前:
MyService.ApiService - サポートされているアカウントの種類: この組織ディレクトリ内のアカウントのみ (シングル テナント)
- 登録 を選択します。
-
名前:
- [API の公開] に移動します>アプリケーション ID URI の横に追加します。
- 既定値 (
api://<client-id>) をそのまま使用するか、カスタマイズします。 - [ スコープの追加] を選択します。
-
スコープ名:
access_as_user - 同意できるユーザー: 管理者とユーザー
- 管理者の同意の表示名: MyService API にアクセスする
- 管理者の同意の説明: サインインしているユーザーの代わりに、アプリが MyService API にアクセスできるようにします。
- [スコープの追加] を選択します。
-
スコープ名:
- 既定値 (
-
アプリケーション (クライアント) ID をコピーします。これは、両方の
appsettings.jsonファイルに必要です。
詳細については、「 クイック スタート: Web API を公開するようにアプリを構成する」を参照してください。
手順 2: Web アプリを登録する
-
アプリの登録>New registration に移動します。
-
名前:
MyService.Web - サポートされているアカウントの種類: この組織のディレクトリ内のアカウントのみ
-
リダイレクト URI:[Web] を選択し、アプリの URL + を入力します
/signin-oidc- ローカル開発の場合:
https://localhost:7001/signin-oidc(実際のポートのlaunchSettings.jsonを確認してください)
- ローカル開発の場合:
- 登録 を選択します。
-
名前:
-
[認証>追加 URI に移動して、(
launchSettings.jsonから) すべての開発 URL を追加します。 -
[証明書とシークレット>クライアント シークレット>新しいクライアント シークレット] に移動します。
- 説明と有効期限を追加します。
- シークレットの値をすぐにコピーします。再び表示されることはありません。
-
API のアクセス許可に移動します>アクセス許可の追加>マイ API。
-
MyService.ApiServiceを選択します。 - [アクセス許可
access_as_user>追加] を選択します。 - [ テナント] の [管理者の同意を付与] を選択します (または、最初の使用時にユーザーにプロンプトが表示されます)。
-
- Web アプリのの
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"]
}
}
Web アプリ (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 管理センター > の概要 > テナント ID |
API ClientId |
アプリの登録 > MyService.ApiService > アプリケーション (クライアント) ID |
Web ClientId |
アプリの登録 > MyService.Web > アプリケーション(クライアント)ID |
Client Secret |
手順 2 で作成 (作成時に直ちにコピー) |
注
アスパイア スターター テンプレートは、WeatherApiClient プロジェクトにMyService.Web クラスを自動的に作成します。 この型指定された HttpClient は、保護された API の呼び出しを示すために、このガイド全体で使用されます。 このクラスは自分で作成する必要はありません。これはテンプレートの一部です。
すばやく開始する
このセクションでは、認証を追加するための要約されたリファレンスを示します。 詳細なチュートリアルについては、 パート 1 と パート 2 を参照してください。
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();
Web アプリ (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処理します。
Important
ログイン ボタンの UserInfo.razor を作成することを忘れないでください。 詳細については、「 Blazor UI コンポーネントの追加 」を参照してください。
注
BlazorAuthenticationChallengeHandlerおよびLoginLogoutEndpointRouteBuilderExtensionsはMicrosoft.Identity.Web (v3.3.0以降)で提供されます。 ファイルのコピーは必要ありません。
変更するファイルを識別する
次の表に、各プロジェクトで変更するファイルの一覧を示します。
| プロジェクト | File | Changes |
|---|---|---|
| ApiService | Program.cs |
JWT Bearer 認証、承認ミドルウェア |
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 |
ログイン ボタン UI (新しいファイル) | |
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 は気象データをレンダリングします。
ソリューション構造を確認する
アスパイア スターター テンプレートでは、次のプロジェクト レイアウトが作成されます。
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
パート 1: Microsoftを使用して API バックエンドをセキュリティで保護する。Identity.Web
このセクションでは、Microsoft Entraによって発行された JWT ベアラー トークンを検証するように API プロジェクトを構成します。
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: 有効なトークン対象ユーザー (通常はアプリ ID URI)
API Program.csの更新
JWT Bearer 認証を追加し、エンドポイントを保護するために、 MyService.ApiService/Program.cs の内容を次のコードに置き換えます。
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
パート 2: 認証用に 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: Web アプリ登録 ID (API ID ではない) -
ClientCredentials: トークンを取得するための Web アプリの資格情報。 複数の資格情報の種類をサポートします。 運用対応オプションについては、「 資格情報の概要」 を参照してください。 -
Scopes: API のアプリ ID URI とサフィックス/.default一致する必要があります
Warnung
運用環境では、クライアント シークレットの代わりに証明書またはマネージド ID を使用します。 推奨される方法については、「 証明書なしの認証 」を参照してください。
Web アプリのProgram.csを更新する
OIDC 認証、トークン取得、およびダウンストリーム API クライアントを構成するために、 MyService.Web/Program.cs の内容を次のコードに置き換えます。
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 サーバーでの増分同意と条件付きアクセスを処理します -
AddMicrosoftIdentityMessageHandler: ベアラー トークンを HttpClient 要求に自動的にアタッチします -
https+http://apiservice: Aspire のサービスディスカバリにより実際の API URL が解決されます。 -
ミドルウェアの順序: エンドポイント
UseAuthentication()→UseAuthorization()→
AddMicrosoftIdentityMessageHandler拡張機能では、複数の構成パターンがサポートされています。
オプション 1: appsettings.json からの構成 (前述)
.AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));
オプション 2: アクション デリゲートを使用したインライン構成
.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 コンポーネントを追加する
Important
この手順は頻繁に忘れ去られています。 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>
AuthorizeRouteView の Routes.razor を更新する
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 サーバーでは、条件付きアクセスと同意のために明示的な例外処理が必要です。 アプリが事前に認証され、すべてのスコープを事前に要求していない限り、ダウンストリーム API を呼び出すすべてのページで MicrosoftIdentityWebChallengeUserException を処理する必要があります。
次の 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セクションとAzureAdセクションでWeatherApiを更新しました - [ ] 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 があります (プレースホルダーなし)
テストとトラブルシューティング
実装が完了したら、アプリケーションを実行し、エンドツーエンドの認証フローを確認します。
アプリケーションを実行する
Web プロジェクトと API プロジェクトの両方を起動するために、アスパイア AppHost を起動します。
# 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 →ブラウザーを開きます (URL については、アスパイア ダッシュボードを確認してください)。
- [
Login → Microsoft Entra でサインインします。 - [天気] ページに移動します。
- (保護された API からの) 気象データの読み込みを確認します。
一般的な問題を解決
次の表に、頻繁に発生する問題とその解決策を示します。
| 問題点 | ソリューション |
|---|---|
| API 呼び出しの 401 |
appsettings.jsonのスコープが API のアプリ ID URI と一致するかどうかを確認する |
| OIDC リダイレクトが失敗する | リダイレクト URI に /signin-oidc を Microsoft Entra に追加する |
| トークンがアタッチされていない |
AddMicrosoftIdentityMessageHandlerでHttpClientが確実に呼び出されるようにしてください。 |
| サービスの検出が失敗する |
AppHost.cs両方のプロジェクトの参照を確認し、それらが実行中であることを確認する |
| AADSTS65001 | 管理者の同意が必要 - Microsoft Entra 管理センターで同意を付与する |
| ログインボタンなし |
UserInfo.razorが存在し、MainLayout.razorに含まれることを確認する |
| 同意ループ |
HandleExceptionAsyncで try/catch がすべての API 呼び出しページにあることを確認する |
MSAL ログを有効にする
認証の問題のトラブルシューティングを行うときは、詳細な MSAL ログを有効にして、トークン取得の詳細を表示します。 次のログ レベルを appsettings.jsonに追加します。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Identity": "Debug",
"Microsoft.IdentityModel": "Debug"
}
}
}
Warnung
非常に詳細な場合があるため、運用環境でデバッグ ログを無効にします。
トークンを検査する
トークンの問題をデバッグするには、 jwt.ms で JWT をデコードし、次のことを確認します。
-
aud(対象ユーザー): API のクライアント ID またはアプリ 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 でスコープを検証する
次の RequireScopeをチェーンして、API が特定のスコープを持つトークンのみを受け入れるようにします。
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での運用環境のデプロイでは、クライアント シークレットの代わりにマネージド ID を使用します。
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 を呼び出す必要がある場合は、 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 の呼び出し」を参照してください。