Web アプリからダウンストリーム API を呼び出す

このガイドでは、Microsoftを使用して、ASP.NET Coreおよび OWIN Web アプリケーションからダウンストリーム API を呼び出す方法について説明します。Identity.Web。 Web アプリでは、 サインインしているユーザーに代わってトークンを 取得し、委任されたアクセス許可を持つ API を呼び出します。

トークン フローを理解する

ユーザーが Web アプリケーションにサインインすると、代わりにダウンストリーム API (Microsoft Graph、Azure サービス、またはカスタム API) を呼び出すことができます。 Microsoft。Identity.Web は、トークンの取得、キャッシュ、自動更新を処理します。

ユーザー トークン フローを確認する

sequenceDiagram
    participant User as User Browser
    participant WebApp as Your Web App
    participant AzureAD as Microsoft Entra ID
    participant API as Downstream API

    User->>WebApp: 1. Access page requiring API data
    Note over WebApp: User already signed in
    WebApp->>AzureAD: 2. Request access token for API<br/>(using user's refresh token)
    AzureAD->>AzureAD: 3. Validate & check consent
    AzureAD->>WebApp: 4. Return access token
    Note over WebApp: Cache token
    WebApp->>API: 5. Call API with token
    API->>WebApp: 6. Return data
    WebApp->>User: 7. Render page with data

前提条件の確認

開始する前に、環境が次の要件を満たしていることを確認します。

  • OpenID Connect 認証で構成された Web アプリ
  • ユーザー サインインの動作
  • API アクセス許可が構成されたアプリの登録
  • ユーザーの同意が得られた(または管理者の同意が与えられた)

ASP.NET Coreを実装する

1. 認証とトークンの取得を構成する

認証サービスを追加し、 Program.cs ファイルでトークンの取得を有効にします。

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

var builder = WebApplication.CreateBuilder(args);

// Add authentication with explicit scheme
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddRazorPages()
    .AddMicrosoftIdentityUI();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

var app = builder.Build();

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

app.MapRazorPages();
app.Run();

2. appsettings.json を構成する

appsettings.json で、Microsoft Entra ID アプリの登録とダウンストリーム API の設定を定義します。

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "CallbackPath": "/signin-oidc",
    "SignedOutCallbackPath": "/signout-callback-oidc",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "your-client-secret"
      }
    ]
  },
  "DownstreamApis": {
    "GraphAPI": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": ["user.read", "mail.read"]
    },
    "MyAPI": {
      "BaseUrl": "https://myapi.example.com",
      "Scopes": ["api://my-api-id/access_as_user"]
    }
  }
}

大事な: ダウンストリーム API を呼び出す Web アプリの場合は、サインイン構成に加えて クライアント資格情報 (証明書またはシークレット) が必要です。

3. ダウンストリーム API のサポートを追加する

ダウンストリーム API を登録するには、次のいずれかのオプションを選択します。

オプション A: 名前付き API を登録する

次のコードは、構成から複数のダウンストリーム API を登録します。

using Microsoft.Identity.Web;

// Register multiple downstream APIs
builder.Services.AddDownstreamApis(
    builder.Configuration.GetSection("DownstreamApis"));

オプション B: Microsoft Graph ヘルパーを使用します

次のコードは、Microsoft Graph SDK クライアントを構成から登録します。

// Install: Microsoft.Identity.Web.GraphServiceClient
builder.Services.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApis:GraphAPI"));

4. コントローラーからダウンストリーム API を呼び出す

IDownstreamApiをコントローラーに挿入し、サインインしているユーザーの代わりに API を呼び出します。

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

[Authorize]
public class ProfileController : Controller
{
    private readonly IDownstreamApi _downstreamApi;
    private readonly ILogger<ProfileController> _logger;

    public ProfileController(
        IDownstreamApi downstreamApi,
        ILogger<ProfileController> logger)
    {
        _downstreamApi = downstreamApi;
        _logger = logger;
    }

    public async Task<IActionResult> Index()
    {
        try
        {
            // Call downstream API on behalf of user
            var userData = await _downstreamApi.GetForUserAsync<UserData>(
                "MyAPI",
                options => options.RelativePath = "api/profile");

            return View(userData);
        }
        catch (MicrosoftIdentityWebChallengeUserException ex)
        {
            // Incremental consent required
            // Redirect user to consent page
            return Challenge(
                new AuthenticationProperties
                {
                    RedirectUri = "/Profile"
                },
                OpenIdConnectDefaults.AuthenticationScheme);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to call downstream API");
            return View("Error");
        }
    }
}

5. Razor ページからダウンストリーム API を呼び出す

Razor ページ モデルに IDownstreamApi を挿入し、API を呼び出します。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
public class ProfileModel : PageModel
{
    private readonly IDownstreamApi _downstreamApi;

    public UserData UserData { get; set; }

    public ProfileModel(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task OnGetAsync()
    {
        try
        {
            UserData = await _downstreamApi.GetForUserAsync<UserData>(
                "MyAPI",
                options => options.RelativePath = "api/profile");
        }
        catch (MicrosoftIdentityWebChallengeUserException)
        {
            // Handle incremental consent
            // User will be redirected to consent page
            throw;
        }
    }
}

Microsoft Graph を呼び出す

Microsoft Graph API呼び出しの場合は、専用の GraphServiceClient を使用します。

パッケージをインストールする

Microsoft.Identity.Web 用の Microsoft Graph パッケージをインストールします。

dotnet add package Microsoft.Identity.Web.GraphServiceClient

スタートアップ コードで Graph クライアントを構成します。

// Startup configuration
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraph(options =>
    {
        options.Scopes = "user.read mail.read";
    })
    .AddInMemoryTokenCaches();

Graph APIを呼び出す

GraphServiceClientをコントローラーに挿入して、Microsoft Graphエンドポイントを呼び出します。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;

[Authorize]
{
    private readonly GraphServiceClient _graphClient;

    public HomeController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }

    public async Task<IActionResult> Index()
    {
        // Get current user's profile
        var user = await _graphClient.Me.GetAsync();

        // Get user's emails
        var messages = await _graphClient.Me.Messages
            .GetAsync(config => config.QueryParameters.Top = 10);

        return View(new { User = user, Messages = messages });
    }
}

Microsoft Graph統合の詳細については


Azure SDK クライアントを呼び出す

Azure サービスを呼び出す場合は、MicrosoftIdentityTokenCredential を使用します。

パッケージをインストールする

必要なAzure SDK パッケージをインストールします。

dotnet add package Microsoft.Identity.Web.Azure
dotnet add package Azure.Storage.Blobs

Microsoft Entra トークン資格情報をスタートアップ コードに登録します。

using Microsoft.Identity.Web;

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

// Add Azure token credential
builder.Services.AddMicrosoftIdentityAzureTokenCredential();

Azure サービスにアクセスする

トークン資格情報を挿入し、Azure SDKクライアントで使用します。

using Azure.Storage.Blobs;
using Microsoft.Identity.Web;

public class StorageController : Controller
{
    private readonly MicrosoftIdentityTokenCredential _credential;

    public StorageController(MicrosoftIdentityTokenCredential credential)
    {
        _credential = credential;
    }

    [Authorize]
    public async Task<IActionResult> ListBlobs()
    {
        var blobClient = new BlobServiceClient(
            new Uri("https://myaccount.blob.core.windows.net"),
            _credential);

        var container = blobClient.GetBlobContainerClient("mycontainer");
        var blobs = new List<string>();

        await foreach (var blob in container.GetBlobsAsync())
        {
            blobs.Add(blob.Name);
        }

        return View(blobs);
    }
}

Azure SDK統合の詳細についてはを参照してください>


IDownstreamApi を使用してカスタム API を呼び出す

独自の REST API の場合、 IDownstreamApi は、構成に基づく単純なアプローチを提供します。

API を構成する

appsettings.jsonでダウンストリーム API 設定を定義します。

{
  "DownstreamApis": {
    "MyAPI": {
      "BaseUrl": "https://myapi.example.com",
      "Scopes": ["api://my-api-id/access_as_user"],
      "RequestAppToken": false
    }
  }
}

GET 要求を送信する

省略可能なクエリ パラメーターを使用してダウンストリーム API からデータを取得します。

// Simple GET
var data = await _downstreamApi.GetForUserAsync<MyData>(
    "MyAPI",
    options => options.RelativePath = "api/resource");

// GET with query parameters
var results = await _downstreamApi.GetForUserAsync<SearchResults>(
    "MyAPI",
    options =>
    {
        options.RelativePath = "api/search";
        options.QueryParameters = new Dictionary<string, string>
        {
            ["query"] = "test",
            ["limit"] = "10"
        };
    });

POST 要求を送信する

要求本文を投稿して、ダウンストリーム API に新しいリソースを作成します。

var newItem = new CreateItemRequest
{
    Name = "New Item",
    Description = "Item description"
};

var created = await _downstreamApi.PostForUserAsync<CreateItemRequest, CreatedItem>(
    "MyAPI",
    newItem,
    options => options.RelativePath = "api/items");

PUT 要求と DELETE 要求を送信する

ダウンストリーム API のリソースを更新または削除します。

// PUT request
var updated = await _downstreamApi.PutForUserAsync<UpdateRequest, UpdatedItem>(
    "MyAPI",
    updateData,
    options => options.RelativePath = "api/items/123");

// DELETE request
await _downstreamApi.DeleteForUserAsync(
    "MyAPI",
    null,
    options => options.RelativePath = "api/items/123");

カスタム API 呼び出しの詳細


IAuthorizationHeaderProvider を使用する (詳細)

HTTP 要求を最大限に制御する場合は、 IAuthorizationHeaderProviderを使用します。

HTTP クライアントを登録する

ダウンストリーム API の名前付き HTTP クライアントを登録します。

builder.Services.AddHttpClient("MyAPI", client =>
{
    client.BaseAddress = new Uri("https://myapi.example.com");
});

カスタム HTTP 要求を作成する

カスタム ヘッダーと承認を使用して HTTP 要求をビルドして送信します。

using Microsoft.Identity.Abstractions;

public class CustomApiService
{
    private readonly IAuthorizationHeaderProvider _authProvider;
    private readonly IHttpClientFactory _httpClientFactory;

    public CustomApiService(
        IAuthorizationHeaderProvider authProvider,
        IHttpClientFactory httpClientFactory)
    {
        _authProvider = authProvider;
        _httpClientFactory = httpClientFactory;
    }

    public async Task<MyData> GetDataAsync()
    {
        // Get authorization header
        var authHeader = await _authProvider.CreateAuthorizationHeaderForUserAsync(
            new[] { "api://my-api-id/access_as_user" });

        // Create HTTP request with custom logic
        var client = _httpClientFactory.CreateClient("MyAPI");
        var request = new HttpRequestMessage(HttpMethod.Get, "api/resource");
        request.Headers.Add("Authorization", authHeader);
        request.Headers.Add("X-Custom-Header", "custom-value");

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<MyData>();
    }
}

カスタム HTTP ロジックの詳細


ダウンストリーム API を呼び出すときに、ユーザーの操作が必要なシナリオをアプリケーションで処理することが必要になる場合があります。 これは、次の 3 つの主なシナリオで発生します。

  1. 増分同意 - 最初に付与されたアクセス許可を超える追加のアクセス許可を要求する
  2. 条件付きアクセス - MFA、デバイス コンプライアンス、場所ポリシーなどのセキュリティ要件を満たす
  3. トークン キャッシュの排除 - アプリケーションの再起動またはキャッシュの有効期限後のトークン キャッシュの再構築

Microsoft。Identity.Web では、最小限のコードでこれらのシナリオを自動的に処理できます。

フローを理解する

Microsoft.Identity.Web はユーザーの操作が必要であることを検出すると、MicrosoftIdentityWebChallengeUserExceptionをスローします。 フレームワークは、 [AuthorizeForScopes] 属性または MicrosoftIdentityConsentAndConditionalAccessHandler サービス (Blazor の場合) を介してこれを自動的に処理します。これは次のとおりです。

  1. 同意/認証のためにユーザーをMicrosoft Entra IDにリダイレクトします
  2. 元の要求 URL を保持します
  3. フローを完了した後、ユーザーを目的の宛先に返します。
  4. 新しく取得したトークンをキャッシュします

前提条件の確認

同意の自動処理を有効にするには、 Program.cs に次の構成が含まれていることを確認します。

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("MyAPI", builder.Configuration.GetSection("MyAPI"))
    .AddInMemoryTokenCaches();

// For MVC applications - enables the account controller
builder.Services.AddControllersWithViews()
    .AddMicrosoftIdentityUI();

// Ensure routes are mapped
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers(); // Required for AccountController

MVC コントローラーに [AuthorizeForScopes] を適用する

コントローラーまたはコントローラーアクションに設定された [AuthorizeForScopes] 属性は、追加のアクセス許可が必要になったときにユーザーにチャレンジすることで、 MicrosoftIdentityWebChallengeUserException を自動的に処理します。

スコープをインラインで宣言する

属性で必要なスコープを直接指定します。

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

[Authorize]
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public class ProfileController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public ProfileController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        // AuthorizeForScopes automatically handles consent challenges
        var userData = await _downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");

        return View(userData);
    }

    // Different action requires additional scopes
    [AuthorizeForScopes(Scopes = new[] { "user.read", "mail.read" })]
    public async Task<IActionResult> Emails()
    {
        var emails = await _downstreamApi.GetForUserAsync<EmailList>(
            "GraphAPI",
            options => options.RelativePath = "me/messages");

        return View(emails);
    }
}

appsettings からスコープを構成する

保守容易性を向上するために、 appsettings.json にスコープを格納します。

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "common",
    "ClientId": "[Your-Client-ID]",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "[Your-Client-Secret]"
      }
    ]
  },
  "DownstreamApis": {
    "TodoList": {
      "BaseUrl": "https://localhost:5001",
      "Scopes": [ "api://[API-Client-ID]/access_as_user" ]
    },
    "GraphAPI": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": [ "https://graph.microsoft.com/Mail.Read", "https://graph.microsoft.com/Mail.Send" ]
    }
  }
}

コントローラー:

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:TodoList:Scopes:0")]
public class TodoListController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public TodoListController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        var todos = await _downstreamApi.GetForUserAsync<IEnumerable<TodoItem>>(
            "TodoList",
            options => options.RelativePath = "api/todolist");

        return View(todos);
    }

    [AuthorizeForScopes(ScopeKeySection = "DownstreamApis:GraphAPI:Scopes:0")]
    public async Task<IActionResult> EmailTodos()
    {
        // If user hasn't consented to Mail.Send, they'll be prompted
        await _downstreamApi.PostForUserAsync<EmailMessage, object>(
            "GraphAPI",
            new EmailMessage { /* ... */ },
            options => options.RelativePath = "me/sendMail");

        return RedirectToAction("Index");
    }
}

ユーザー フローを使用してMicrosoft Entra 外部 IDを構成する

複数のユーザー フローを持つ外部 ID (B2C) アプリケーションの場合は、属性でユーザー フローを指定します。

[Authorize]
public class AccountController : Controller
{
    private const string SignUpSignInFlow = "b2c_1_susi";
    private const string EditProfileFlow = "b2c_1_edit_profile";
    private const string ResetPasswordFlow = "b2c_1_reset";

    [AuthorizeForScopes(
        ScopeKeySection = "DownstreamApis:TodoList:Scopes:0",
        UserFlow = SignUpSignInFlow)]
    public async Task<IActionResult> Index()
    {
        var data = await _downstreamApi.GetForUserAsync<UserData>(
            "TodoList",
            options => options.RelativePath = "api/data");

        return View(data);
    }

    [AuthorizeForScopes(
        Scopes = new[] { "openid", "offline_access" },
        UserFlow = EditProfileFlow)]
    public async Task<IActionResult> EditProfile()
    {
        // This triggers the B2C edit profile flow
        return RedirectToAction("Index");
    }
}

Razor ページに [AuthorizeForScopes] を適用する

ページ モデル クラスに [AuthorizeForScopes] を適用します。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:MyAPI:Scopes:0")]
public class IndexModel : PageModel
{
    private readonly IDownstreamApi _downstreamApi;

    public UserData UserData { get; set; }

    public IndexModel(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task OnGetAsync()
    {
        // Automatically handles consent challenges
        UserData = await _downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");
    }
}

Blazor サーバー アプリケーションには、 MicrosoftIdentityConsentAndConditionalAccessHandler サービスを使用した明示的な例外処理が必要です。

Program.csの構成

スタートアップ コードで Blazor Server の同意ハンドラーを登録します。

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApis("TodoList", builder.Configuration.GetSection("DownstreamApis"))
    .AddInMemoryTokenCaches();

// Register the consent handler for Blazor
builder.Services.AddServerSideBlazor()
    .AddMicrosoftIdentityConsentHandler();

Blazor コンポーネントを作成する

API 呼び出しを try-catch ブロックでラップし、 ConsentHandler.HandleException() を使用して同意チャレンジを処理します。

@page "/todolist"
@using Microsoft.Identity.Web
@using Microsoft.Identity.Abstractions
@using MyApp.Models

@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
@inject IDownstreamApi DownstreamApi

<h3>My Todo List</h3>

@if (todos == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <ul>
        @foreach (var todo in todos)
        {
            <li>@todo.Title</li>
        }
    </ul>
}

@code {
    private IEnumerable<TodoItem> todos;

    protected override async Task OnInitializedAsync()
    {
        await LoadTodosAsync();
    }

    [AuthorizeForScopes(ScopeKeySection = "DownstreamApis:TodoList:Scopes:0")]
    private async Task LoadTodosAsync()
    {
        try
        {
            todos = await DownstreamApi.GetForUserAsync<IEnumerable<TodoItem>>(
                "TodoList",
                options => options.RelativePath = "api/todolist");
        }
        catch (Exception ex)
        {
            // Handles MicrosoftIdentityWebChallengeUserException
            // and initiates user consent/authentication flow
            ConsentHandler.HandleException(ex);
        }
    }

    private async Task AddTodoAsync(string title)
    {
        try
        {
            await DownstreamApi.PostForUserAsync<TodoItem, TodoItem>(
                "TodoList",
                new TodoItem { Title = title },
                options => options.RelativePath = "api/todolist");

            await LoadTodosAsync();
        }
        catch (Exception ex)
        {
            ConsentHandler.HandleException(ex);
        }
    }
}

例外を手動で処理する (詳細)

カスタム同意フロー ロジックが必要な場合は、 MicrosoftIdentityWebChallengeUserException を明示的に処理します。

[Authorize]
public class AdvancedController : Controller
{
    private readonly IDownstreamApi _downstreamApi;
    private readonly ILogger<AdvancedController> _logger;

    public AdvancedController(
        IDownstreamApi downstreamApi,
        ILogger<AdvancedController> logger)
    {
        _downstreamApi = downstreamApi;
        _logger = logger;
    }

    public async Task<IActionResult> SendEmail()
    {
        try
        {
            await _downstreamApi.PostForUserAsync<EmailMessage, object>(
                "GraphAPI",
                new EmailMessage
                {
                    Subject = "Test",
                    Body = "Test message"
                },
                options => options.RelativePath = "me/sendMail");

            return RedirectToAction("Success");
        }
        catch (MicrosoftIdentityWebChallengeUserException ex)
        {
            // Log the consent requirement
            _logger.LogWarning(
                "Consent required for scopes: {Scopes}. Challenging user.",
                string.Join(", ", ex.Scopes));

            // Custom properties for redirect
            var properties = new AuthenticationProperties
            {
                RedirectUri = Url.Action("SendEmail", "Advanced"),
            };

            // Add custom state if needed
            properties.Items["consent_attempt"] = "1";

            return Challenge(properties, OpenIdConnectDefaults.AuthenticationScheme);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to send email");
            return View("Error");
        }
    }
}

条件付きアクセスのシナリオを処理する

条件付きアクセス ポリシーでは、追加の認証要素が必要な場合があります。 処理は、増分同意と同じです。

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:SecureAPI:Scopes:0")]
public class SecureDataController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public SecureDataController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        // If conditional access requires MFA, AuthorizeForScopes
        // automatically challenges the user
        var sensitiveData = await _downstreamApi.GetForUserAsync<SensitiveData>(
            "SecureAPI",
            options => options.RelativePath = "api/sensitive");

        return View(sensitiveData);
    }
}

一般的な条件付きアクセス トリガー:

  • 多要素認証 (MFA)
  • 準拠しているデバイスの要件
  • 信頼されたネットワークの場所
  • 利用規約への同意
  • パスワード変更の要件

ベスト プラクティスに従う

同意と条件付きアクセスの処理を実装するときに、これらの推奨事項を適用します。

[AuthorizeForScopes]を使用する- MVC コントローラーと Razor ページの最も簡単なアプローチ

構成にスコープを格納する - ScopeKeySection = "DownstreamApis:ApiName:Scopes:0" を使用してスコープを参照する appsettings.json

コントローラー レベルで適用 - コントローラー に既定のスコープを設定し、特定のアクションでオーバーライドする

Blazor で例外を処理 する - try-catch と use を使用して API 呼び出しを常にラップする ConsentHandler.HandleException()

例外を再スローする - MicrosoftIdentityWebChallengeUserExceptionをキャッチした場合は、[AuthorizeForScopes]が処理できるようにそれを再スローする

条件付きアクセスのテスト - アプリが MFA やその他の CA ポリシーを正しく処理するかどうかを確認する

例外を抑止しない - 再スローせずにキャッチすると、同意の流れが中断される

無期限に応答をキャッシュしない - トークンの有効期限が切れます。再認証の設計


静的アクセス許可 (管理者の同意)

すべてのアクセス許可は、アプリの登録中に要求され、テナント管理者によって同意されます。

長所:

  • ユーザーに同意のプロンプトが表示されない
  • ファースト パーティのMicrosoft アプリに必要
  • よりシンプルなユーザー エクスペリエンス

短所:

  • テナント管理者の関与が必要
  • 最初から過剰な特権を持つ
  • マルチテナント シナリオに対する柔軟性が低い

環境設定:

// Request all pre-approved scopes for Microsoft Graph
var scopes = new[] { "https://graph.microsoft.com/.default" };

var userData = await _downstreamApi.GetForUserAsync<UserData>(
    "GraphAPI",
    options =>
    {
        options.RelativePath = "me";
        options.Scopes = scopes; // Use .default scope
    });

増分同意 (動的)

アクセス許可は、実行時に必要に応じて要求されます。

長所:

  • セキュリティの向上 (最小限の特権の原則)
  • ユーザーが実際に使用するものに同意する
  • マルチテナント アプリに対して機能します

短所:

  • 同意プロンプトでユーザーが中断される場合がある
  • 処理が必要です MicrosoftIdentityWebChallengeUserException

推薦: マルチテナント アプリケーションに増分同意を使用する。管理者の同意が保証されているファースト パーティのエンタープライズ アプリに静的アクセス許可を使用する


トークン キャッシュの構成

Microsoft。Identity.Web は、パフォーマンスを向上させ、Microsoft Entraの呼び出しを減らすためにトークンをキャッシュします。

メモリ内キャッシュを使用する (既定)

開発シナリオまたは単一サーバー シナリオ用のメモリ内トークン キャッシュを追加します。

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches(); // In-memory cache

次の用途に使用します。

  • 発達
  • 単一サーバーの展開
  • 小さなユーザー ベース

Limitations:

  • インスタンス間で共有されない
  • アプリの再起動時に失われた
  • ユーザーによるメモリ使用量の増加

実稼働デプロイ用に Redis やSQL Serverなどの分散キャッシュを構成します。

// Install: Microsoft.Identity.Web.TokenCache

// Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration["Redis:ConnectionString"];
    options.InstanceName = "MyApp_";
});

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

// SQL Server
builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration["SqlCache:ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TokenCache";
});

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

次の用途に使用します。

  • マルチサーバー展開 (負荷分散)
  • 高可用性のシナリオ
  • 大規模なユーザー ベース
  • 再起動後の永続キャッシュ

トークン取得エラーの処理

一般的な例外をキャッチする

次のコードは、最も一般的なトークン取得例外をキャッチして処理する方法を示しています。

try
{
    var data = await _downstreamApi.GetForUserAsync<MyData>(
        "MyAPI",
        options => options.RelativePath = "api/resource");
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
    // User needs to consent or reauthenticate
    _logger.LogWarning($"User consent required: {ex.Message}");
    return Challenge(new AuthenticationProperties { RedirectUri = Request.Path });
}
catch (MsalUiRequiredException ex)
{
    // User interaction required (sign-in again, MFA, etc.)
    _logger.LogWarning($"User interaction required: {ex.Message}");
    return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
}
catch (MsalServiceException ex)
{
    // Service error (Microsoft Entra ID unavailable, etc.)
    _logger.LogError(ex, "Microsoft Entra ID service error");
    return StatusCode(503, "Authentication service temporarily unavailable");
}
catch (HttpRequestException ex)
{
    // Downstream API unreachable
    _logger.LogError(ex, "Downstream API call failed");
    return StatusCode(503, "Downstream service unavailable");
}

優雅な劣化を実装する

ダウンストリーム API からオプションのデータを読み込み、呼び出しが失敗したときに既定値にフォールバックします。

public async Task<IActionResult> Dashboard()
{
    var model = new DashboardModel();

    // Try to load optional data from downstream API
    try
    {
        model.EnrichedData = await _downstreamApi.GetForUserAsync<EnrichedData>(
            "MyAPI",
            options => options.RelativePath = "api/enriched");
    }
    catch (Exception ex)
    {
        _logger.LogWarning(ex, "Failed to load enriched data, using defaults");
        model.EnrichedData = new EnrichedData { /* defaults */ };
    }

    return View(model);
}

OWIN の実装 (.NET Framework)

.NET Framework 上の OWIN ベースの Web アプリケーションの場合は、次の手順に従います。

1. パッケージをインストールする

必要な NuGet パッケージをインストールします。

Install-Package Microsoft.Identity.Web.OWIN
Install-Package Microsoft.Owin.Host.SystemWeb

2.スタートアップの構成

OWIN スタートアップ クラスMicrosoft Entra認証とトークン取得を構成します。

using Microsoft.Identity.Web;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        app.AddMicrosoftIdentityWebApp(
            Configuration,
            configSectionName: "AzureAd",
            openIdConnectScheme: "OpenIdConnect",
            cookieScheme: CookieAuthenticationDefaults.AuthenticationType,
            subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true);

        app.EnableTokenAcquisitionToCallDownstreamApi();
        app.AddDistributedTokenCaches();
    }
}

3. ダウンストリーム API を呼び出す

トークンを取得し、MVC コントローラーからダウンストリーム API を呼び出します。

using Microsoft.Identity.Web;
using System.Threading.Tasks;
using System.Web.Mvc;

[Authorize]
public class ProfileController : Controller
{
    public async Task<ActionResult> Index()
    {
        var downstreamApi = TokenAcquirerFactory.GetDefaultInstance()
            .GetTokenAcquirer()
            .GetDownstreamApi();

        var userData = await downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");

        return View(userData);
    }
}

Note: OWIN のサポートには、ASP.NET Coreといくつかの違いがあります。 詳細については 、OWIN のドキュメント を参照してください。


セキュリティのベスト プラクティスに従う

スコープの管理

API のアクセス許可を要求する場合は、最小限の特権の原則を適用します。

するべきこと

  • 必要なスコープのみを要求する
  • 高度な機能に増分同意を使用する
  • アプリで必要なスコープを文書化する

してはいけないこと

  • 不要なスコープを事前に要求する
  • 正当な理由なしで管理者専用スコープを要求する
  • すべてのスコープが付与されると仮定する

トークンを安全に処理する

アプリケーションのアクセス トークンを保護するには、次のガイドラインに従います。

するべきこと

  • Microsoft.Identity.Web にトークン管理を任せる
  • 運用環境で分散キャッシュを使用する
  • トークン取得エラーを適切に処理する

してはいけないこと

  • トークンを自分で保存する
  • ログ アクセス トークン
  • クライアント側コードにトークンを送信する

エラーを処理する

認証と API 呼び出しの失敗に対して堅牢なエラー処理を実装します。

するべきこと

  • 同意例外をキャッチして処理する
  • ユーザーに明確なエラー メッセージを提供する
  • デバッグのエラーをログに記録する

してはいけないこと

  • トークン エラーをユーザーに公開する
  • API 呼び出しが黙って失敗する
  • 認証の例外を無視する

一般的な問題のトラブルシューティング

頻繁に発生する認証エラーについては、これらのソリューションを確認してください。

問題: "AADSTS65001: ユーザーまたは管理者が同意していません"

原因: ユーザーが必要なスコープに同意していない。

Solution:

catch (MicrosoftIdentityWebChallengeUserException ex)
{
    // Redirect to consent page
    return Challenge(
        new AuthenticationProperties { RedirectUri = Request.Path },
        OpenIdConnectDefaults.AuthenticationScheme);
}

問題: "AADSTS50076: 多要素認証が必要"

原因: ユーザーは MFA を完了する必要があります。

Solution:

catch (MsalUiRequiredException)
{
    // Redirect user to sign in with MFA
    return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
}

問題: アプリの再起動後もトークンが保持されない

原因: メモリ内キャッシュの使用。

Solution: 分散キャッシュ (Redis、SQL Server、または Cosmos DB) に切り替えます。

問題: 401 ダウンストリーム API から承認されていません

考えられる原因:

  • 要求されたスコープが間違っている
  • アプリの登録で付与されていない API アクセス許可
  • トークンの有効期限が切れています

Solution:

  1. API 要件に一致するスコープが appsettings.json にあるかを確認する
  2. アプリの登録に API アクセス許可があることを確認する
  3. トークンがキャッシュおよび更新されていることを確認する

詳細な診断の場合: 相関 ID、トークン キャッシュのデバッグ、包括的なトラブルシューティング パターンについては、「 ログ記録と診断ガイド 」を参照してください。


パフォーマンスの最適化

トークン キャッシュ戦略を計画する

デプロイ トポロジに一致するキャッシュ戦略を選択します。

  • マルチサーバー展開に分散キャッシュを使用する
  • 適切なキャッシュの有効期限を構成する
  • キャッシュ パフォーマンスを監視する

トークン要求を最小限に抑える

Microsoft。Identity.Web はトークンを自動的にキャッシュします。 次の例の両方の呼び出しでは、同じキャッシュされたトークンが再利用されます。

// Bad: Multiple token acquisitions
var profile = await _downstreamApi.GetForUserAsync<Profile>(
    "API",
    options => options.RelativePath = "profile");
var settings = await _downstreamApi.GetForUserAsync<Settings>(
    "API",
    options => options.RelativePath = "settings");

// Good: Single token, multiple calls (token is cached)
// Both calls use the same cached token
var profile = await _downstreamApi.GetForUserAsync<Profile>(
    "API",
    options => options.RelativePath = "profile");
var settings = await _downstreamApi.GetForUserAsync<Settings>(
    "API",
    options => options.RelativePath = "settings");

並列 API 呼び出しを行う

複数のダウンストリーム API を同時に呼び出して、全体的な待機時間を短縮します。

// Call multiple APIs in parallel
var profileTask = _downstreamApi.GetForUserAsync<Profile>(
    "API1",
    options => options.RelativePath = "profile");
var settingsTask = _downstreamApi.GetForUserAsync<Settings>(
    "API2",
    options => options.RelativePath = "settings");

await Task.WhenAll(profileTask, settingsTask);

var profile = profileTask.Result;
var settings = settingsTask.Result;

関連するシナリオに関する追加のガイダンスを参照してください。