.NET Aspire アプリMicrosoft Entra ID認証を追加する

このガイドでは、.NET AspireMicrosoft Entra ID 認証と承認を使用して分散アプリケーションをセキュリティで保護する方法について説明します。 以下が対象です。

  1. Blazor Server フロントエンド (MyService.Web): OpenID Connect とトークン取得を使用したユーザー サインイン
  2. Protected API バックエンド (MyService.ApiService): Microsoft.Identity.Web を使用した JWT 検証
  3. エンド ツー エンド フロー: 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> ( AudiencesScopesで使用)
  • Web App ClientId — Web アプリ登録のアプリケーション (クライアント) ID
  • クライアント シークレット (または証明書) - Web アプリの資格情報 (appsettings.jsonではなく、ユーザー シークレットに格納)
  • スコープ - Web アプリが要求するスコープ (たとえば、 api://<api-client-id>/.defaultapi://<api-client-id>/access_as_user

手順 1: API を登録する

  1. Microsoft Entra 管理センター>Identity>Applications>アプリの登録 に移動します。
  2. [新規登録] を選択します。
    • 名前:MyService.ApiService
    • サポートされているアカウントの種類: この組織ディレクトリ内のアカウントのみ (シングル テナント)
    • 登録 を選択します。
  3. [API の公開] に移動します>アプリケーション ID URI の横に追加します。
    • 既定値 (api://<client-id>) をそのまま使用するか、カスタマイズします。
    • [ スコープの追加] を選択します。
      • スコープ名:access_as_user
      • 同意できるユーザー: 管理者とユーザー
      • 管理者の同意の表示名: MyService API にアクセスする
      • 管理者の同意の説明: サインインしているユーザーの代わりに、アプリが MyService API にアクセスできるようにします。
      • [スコープの追加] を選択します。
  4. アプリケーション (クライアント) ID をコピーします。これは、両方のappsettings.json ファイルに必要です。

詳細については、「 クイック スタート: Web API を公開するようにアプリを構成する」を参照してください。

手順 2: Web アプリを登録する

  1. アプリの登録>New registration に移動します。
    • 名前:MyService.Web
    • サポートされているアカウントの種類: この組織のディレクトリ内のアカウントのみ
    • リダイレクト URI:[Web] を選択し、アプリの URL + を入力します/signin-oidc
      • ローカル開発の場合: https://localhost:7001/signin-oidc (実際のポートの launchSettings.json を確認してください)
    • 登録 を選択します。
  2. [認証>追加 URI に移動して、(launchSettings.json から) すべての開発 URL を追加します。
  3. [証明書とシークレット>クライアント シークレット>新しいクライアント シークレット] に移動します。
    • 説明と有効期限を追加します。
    • シークレットの値をすぐにコピーします。再び表示されることはありません。
  4. API のアクセス許可に移動します>アクセス許可の追加>マイ API。
    • MyService.ApiService を選択します。
    • [アクセス許可 access_as_user>追加] を選択します。
    • [ テナント] の [管理者の同意を付与] を選択します (または、最初の使用時にユーザーにプロンプトが表示されます)。
  5. 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
  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 は気象データをレンダリングします

ソリューション構造を確認する

アスパイア スターター テンプレートでは、次のプロジェクト レイアウトが作成されます。

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 を更新する

RouteViewAuthorizeRouteViewComponents/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}";
            }
        }
    }
}

このパターンは次のように動作します。

  1. IsAuthenticatedAsync() は、API 呼び出しを行う前にユーザーがサインインしているかどうかを確認します。
  2. HandleExceptionAsync()MicrosoftIdentityWebChallengeUserException (または InnerException) をキャッチします。
  3. チャレンジ例外の場合、ユーザーは必要な要求またはスコープで再認証するようにリダイレクトされます。
  4. チャレンジ例外でない場合、 HandleExceptionAsyncfalse を返して、エラーを自分で処理できるようにします。

クライアント シークレットをユーザー シークレットに格納する

.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

認証フローをテストする

  1. Blazor Web UI →ブラウザーを開きます (URL については、アスパイア ダッシュボードを確認してください)。
  2. [Login → Microsoft Entra でサインインします。
  3. [天気] ページに移動します。
  4. (保護された API からの) 気象データの読み込みを確認します。

一般的な問題を解決

次の表に、頻繁に発生する問題とその解決策を示します。

問題点 ソリューション
API 呼び出しの 401 appsettings.jsonのスコープが API のアプリ ID URI と一致するかどうかを確認する
OIDC リダイレクトが失敗する リダイレクト URI に /signin-oidc を Microsoft Entra に追加する
トークンがアタッチされていない AddMicrosoftIdentityMessageHandlerHttpClientが確実に呼び出されるようにしてください。
サービスの検出が失敗する 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");

アプリ専用トークンの使用 (サービス間)

ユーザー コンテキストのないデーモン シナリオまたはサービス間呼び出しの場合は、 RequestAppTokentrue に設定します。

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 の呼び出し」を参照してください。