自動統合テストの実行

開発者は、開発したアプリに対して自動統合テストを実行する必要があります。 自動統合テストで Microsoft ID プラットフォームによって保護されている API (または Microsoft Graph などの保護されている API) を呼び出すことは困難です。 Microsoft Entra ID では、多くの場合、対話型ユーザー サインイン プロンプトが必要になりますが、これは自動化するのが困難です。 この記事では、リソース所有者のパスワード資格情報付与 (ROPC) と呼ばれる非対話型フローを使用して、テストのためにユーザーを自動的にサインインする方法について説明します。

自動統合テストを準備するには、テスト ユーザーを作成し、アプリの登録を作成して構成します。また、テナントに対していくつかの構成変更を加える可能性があります。 これらの手順の一部では、管理特権が必要です。 また、運用環境では ROPC フローを使用 "しない" ことをお勧めします。 自分が管理者である別のテスト テナントを作成して、自動統合テストを安全かつ効果的に実行できるようにします。

警告

運用環境では ROPC フローを使用 "しない" ことをお勧めします。 ほとんどの運用シナリオでは、より安全な代替手段を利用でき、推奨されます。 ROPC フローでは、アプリケーションで非常に高い信頼度が要求されるため、他の認証フローには存在しないリスクが伴います。 このフローは、別のテスト テナントでのテスト目的でのみ使用し、テスト ユーザーのみを対象にしてください。

重要

  • Microsoft ID プラットフォームでは、Microsoft Entra テナント内での ROPC をサポートしています。個人アカウントは対象外です。 そのため、テナント固有のエンドポイント (https://login.microsoftonline.com/{TenantId_or_Name}) または organizations エンドポイントを使用する必要があります。
  • Microsoft Entra テナントに招待された個人アカウントでは、ROPC を使用できません。
  • パスワードがないアカウントは ROPC でサインインできません。つまり、SMS サインイン、FIDO、および Authenticator アプリなどの機能は、そのフローでは動作しません。
  • ユーザーが多要素認証 (MFA) を使用してアプリケーションにログインすると、ログインできずにブロックされます。
  • ROPC はハイブリッド ID フェデレーション シナリオ (たとえば、オンプレミスのアカウントの認証に使用される Microsoft Entra ID や Active Directory Federation Services (AD FS)) ではサポートされていません。 ユーザーがオンプレミスの ID プロバイダーに全ページ リダイレクトされる場合、Microsoft Entra ID ではその ID プロバイダーに対してユーザー名とパスワードをテストできません。 ただし、ROPC ではパススルー認証がサポートされています。
  • ハイブリッド ID フェデレーション シナリオの例外は次のとおりです。ホーム領域の検出ポリシーで AllowCloudPasswordValidation を TRUE に設定すると、オンプレミス パスワードがクラウドと同期されている場合に、ROPC フローはフェデレーション ユーザーに対して機能します。 詳細については、レガシ アプリケーションに対するフェデレーション ユーザーの直接 ROPC 認証を有効にする方法に関する記事を参照してください。

別のテスト テナントを作成する

ROPC 認証フローの使用は運用環境では危険であるため、アプリケーションをテストするために別のテナントを作成します。 既存のテスト テナントを使用できますが、次の手順の一部では管理特権が必要になるため、テナントの管理者である必要があります。

キー コンテナーを作成して構成する

テスト用のユーザー名とパスワードは Azure Key Vault にシークレットとして安全に保存することをお勧めします。 後でテストを実行すると、テストはセキュリティ プリンシパルのコンテキストで実行されます。 セキュリティ プリンシパルは、テストをローカルで (たとえば、Visual Studio または Visual Studio Code で) 実行している場合は、Microsoft Entra ユーザーです。テストを Azure Pipelines または別の Azure リソースで実行している場合は、サービス プリンシパルまたはマネージド ID です。 テスト実行者がキー コンテナーからテスト用のユーザー名とパスワードを取得できるようにするには、セキュリティ プリンシパルに読み取り一覧表示のシークレット アクセス許可が必要です。 詳細については、「Azure Key Vault での認証」を参照してください。

  1. まだない場合は、新しいキー コンテナーを作成します。
  2. コンテナー URI プロパティ値 (https://<your-unique-keyvault-name>.vault.azure.net/ のような形式です) をメモしておきます。これは、この記事の後半のテストの例で使用されます。
  3. テストを実行するセキュリティ プリンシパルのアクセス ポリシーを割り当てます。 ユーザー、サービス プリンシパル、またはマネージド ID に対して、キー コンテナーでの取得一覧表示 のシークレット アクセス許可を付与します。

テスト ユーザーの作成

ヒント

この記事の手順は、開始するポータルによって若干異なる場合があります。

テスト用のテナントにいくつかのテスト ユーザーを作成します。 テスト ユーザーは実際の人間ではないため、複雑なパスワードを割り当て、Azure Key Vault にこれらのパスワードをシークレットとして安全に保存することをお勧めします。

  1. クラウド アプリケーション管理者以上として Microsoft Entra 管理センターにサインインします。
  2. [ID]>[ユーザー]>[すべてのユーザー] の順に移動します。
  3. [新しいユーザー] を選択し、ディレクトリに 1 つ以上のテスト ユーザー アカウントを作成します。
  4. この記事の後半のテストの例では、単一のテスト ユーザーを使用します。 前に作成したキー コンテナーに、テスト用のユーザー名とパスワードをシークレットとして追加します。 "TestUserName" という名前のシークレットとしてユーザー名を、"TestPassword" という名前のシークレットとしてパスワードを追加します。

アプリの登録を作成して構成する

テスト中に API を呼び出すときにクライアント アプリとして機能するアプリケーションを登録します。 これは運用環境に既に存在しているアプリケーションと同じには "しないでください"。 テスト目的でのみ使用する別のアプリを用意する必要があります。

アプリケーションを登録する

アプリの登録を作成する。 アプリの登録に関するクイックスタートの手順に従うことができます。 リダイレクト URI を追加したり、資格情報を追加したりする必要はありません。そのため、それらのセクションは省略できます。

アプリケーション (クライアント) ID をメモしておきます。これは、この記事の後半のテストの例で使用されます。

パブリック クライアント フローに対してアプリを有効にする

ROPC はパブリック クライアント フローであるため、パブリック クライアント フローに対してアプリを有効にする必要があります。 Microsoft Entra 管理センターのアプリ登録から、[認証]>[詳細設定]>[パブリック クライアント フローの許可] に移動します。 切り替えを [はい] に設定します。

ROPC は対話型フローではないため、実行時に同意するよう求める画面は表示されません。 トークンを取得するときのエラーを回避するために、アクセス許可に事前同意します。

アプリにアクセス許可を追加します。 アプリに機密または高特権のアクセス許可を追加しないでください。テスト シナリオの範囲を、Microsoft Entra ID との統合に関する基本的な統合シナリオに限定することをお勧めします。

Microsoft Entra 管理センターのアプリ登録から、[API のアクセス許可]>[アクセス許可の追加] に移動します。 使用する API を呼び出すために必要なアクセス許可を追加します。 この記事の以降のテストの例では、https://graph.microsoft.com/User.Readhttps://graph.microsoft.com/User.ReadBasic.All のアクセス許可を使用します。

アクセス許可が追加されたら、同意する必要があります。 アクセス許可に同意する方法は、テスト アプリがアプリの登録と同じテナントにあるかどうかと、テナントの管理者であるかどうかによって異なります。

アプリとアプリの登録が同じテナントにあり、管理者である場合

アプリを登録したのと同じテナントでアプリをテストし、そのテナントの管理者である場合は、Microsoft Entra 管理センターからアクセス許可に同意できます。 Azure portal のアプリ登録で、[API のアクセス許可] に移動し、[アクセス許可の追加] ボタンの横にある [<your_tenant_name> に管理者の同意を与えます] ボタンを選択し、[はい] をクリックして確定します。

アプリとアプリの登録が異なるテナントにあるか、管理者でない場合

アプリを登録したのと同じテナントでアプリのテストを計画していない場合、またはテナントの管理者でない場合は、Microsoft Entra 管理センターからのアクセス許可に同意することはできません。 ただし、Web ブラウザーでサインイン プロンプトをトリガーすると、一部のアクセス許可に同意できます。

Microsoft Entra 管理センターのアプリ登録で、[認証]>[プラットフォーム構成]>[プラットフォームの追加]>[Web] に移動します。 リダイレクト URI "https://localhost" を追加し、[構成] を選択します。

管理者以外のユーザーが Azure portal で事前同意する方法はないため、ブラウザーで次の要求を送信してください。 ログイン画面が表示されたら、前の手順で作成したテスト アカウントでサインインします。 プロンプトで表示されたアクセス許可に同意します。 呼び出す API や使用するテスト ユーザーごとに、この手順を繰り返す必要がある場合があります。

// Line breaks for legibility only

https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id={your_client_ID}
&response_type=code
&redirect_uri=https://localhost
&response_mode=query
&scope={resource_you_want_to_call}/.default
&state=12345

{tenant} をテナント ID に、{your_client_ID} をアプリケーションのクライアント ID に、{resource_you_want_to_call} を ID URI (たとえば、"https://graph.microsoft.com") またはアクセスする API のアプリ ID に置き換えます。

MFA ポリシーからテスト アプリとユーザーを除外する

テナントには、Microsoft が推奨するように、すべてのユーザーに対して多要素認証 (MFA) を要求する条件付きアクセス ポリシーがある可能性があります。 MFA は ROPC では機能しないため、この要件からテスト アプリケーションとテスト ユーザーを除外する必要があります。

ユーザー アカウントを除外するには:

  1. クラウド アプリケーション管理者以上として Microsoft Entra 管理センターにサインインします。
  2. 左側のナビゲーション ウィンドウで [ID]>[Security Center] を参照し、[条件付きアクセス] を選択します。
  3. [ポリシー] で、MFA を要求する条件付きアクセス ポリシーを選択します。
  4. [Users or workload identities]\(ユーザーまたはワークロード ID)\ を選択します。
  5. [除外] タブを選択し、[ユーザーとグループ] チェックボックスをオンにします。
  6. [対象外とするユーザーの選択] で除外するユーザー アカウントを選択します。
  7. [選択] ボタンを選択し、[保存] をクリックします。

テスト アプリケーションを除外するには:

  1. [ポリシー] で、MFA を要求する条件付きアクセス ポリシーを選択します。
  2. [クラウド アプリまたは操作] を選択します。
  3. [ 除外] タブを選択し、[除外されたクラウド アプリの選択] をクリックします。
  4. [除外されたクラウド アプリの選択] で除外するアプリを選択します。
  5. [選択] ボタンを選択し、[保存] をクリックします。

アプリケーション テストの作成

設定が完了したので、自動テストを作成できます。 次のテストについて示します。

  1. .NET のコード例では、Microsoft Authentication Library (MSAL)xUnit (一般的なテスト フレームワーク) を使用します。
  2. JavaScript のコード例では、Microsoft Authentication Library (MSAL)Playwright (一般的なテスト フレームワーク) を使用します。

appsettings.json ファイルを設定する

以前に作成したテスト アプリのクライアント ID、必要な範囲、キー コンテナーの URI を、テスト プロジェクトの appsettings.json ファイルに追加します。

{
  "Authentication": {
    "AzureCloudInstance": "AzurePublic", //Will be different for different Azure clouds, like US Gov
    "AadAuthorityAudience": "AzureAdMultipleOrgs",
    "ClientId": <your_client_ID>
  },

  "WebAPI": {
    "Scopes": [
      //For this Microsoft Graph example.  Your value(s) will be different depending on the API you're calling
      "https://graph.microsoft.com/User.Read",
      //For this Microsoft Graph example.  Your value(s) will be different depending on the API you're calling
      "https://graph.microsoft.com/User.ReadBasic.All"
    ]
  },

  "KeyVault": {
    "KeyVaultUri": "https://<your-unique-keyvault-name>.vault.azure.net//"
  }
}

すべてのテスト クラスで使用するようにクライアントを設定する

SecretClient () を使用して、Azure Key Vault からテスト用のユーザー名とパスワードのシークレットを取得します。 このコードでは、Key Vault がスロットルされている場合の再試行にはエクスポネンシャル バックオフが使用されています。

DefaultAzureCredential() は、環境変数またはマネージド ID (コードがマネージド ID を使用して Azure リソースで実行されている場合) によって構成されたサービス プリンシパルからアクセス トークンを取得することによって、Azure Key Vault で認証します。 コードがローカルで実行されている場合、DefaultAzureCredential はローカル ユーザーの資格情報を使用します。 詳細については、Azure ID クライアント ライブラリのコンテンツを参照してください。

Microsoft Authentication Library (MSAL) を使用して、ROPC フローを使用して認証し、アクセス トークンを取得します。 アクセス トークンは、HTTP 要求でベアラー トークンとして渡されます。

using Xunit;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using System.Security;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.Extensions.Configuration;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Azure.Core;
using System;

public class ClientFixture : IAsyncLifetime
{
    public HttpClient httpClient;

    public async Task InitializeAsync()
    {
        var builder = new ConfigurationBuilder().AddJsonFile("<path-to-json-file>");

        IConfigurationRoot Configuration = builder.Build();

        var PublicClientApplicationOptions = new PublicClientApplicationOptions();
        Configuration.Bind("Authentication", PublicClientApplicationOptions);
        var app = PublicClientApplicationBuilder.CreateWithApplicationOptions(PublicClientApplicationOptions)
            .Build();

        SecretClientOptions options = new SecretClientOptions()
        {
            Retry =
                {
                    Delay= TimeSpan.FromSeconds(2),
                    MaxDelay = TimeSpan.FromSeconds(16),
                    MaxRetries = 5,
                    Mode = RetryMode.Exponential
                 }
        };

        string keyVaultUri = Configuration.GetValue<string>("KeyVault:KeyVaultUri");
        var client = new SecretClient(new Uri(keyVaultUri), new DefaultAzureCredential(), options);

        KeyVaultSecret userNameSecret = client.GetSecret("TestUserName");
        KeyVaultSecret passwordSecret = client.GetSecret("TestPassword");

        string password = passwordSecret.Value;
        string username = userNameSecret.Value;
        string[] scopes = Configuration.GetSection("WebAPI:Scopes").Get<string[]>();
        SecureString securePassword = new NetworkCredential("", password).SecurePassword;

        AuthenticationResult result = null;
        httpClient = new HttpClient();

        try
        {
            result = await app.AcquireTokenByUsernamePassword(scopes, username, securePassword)
                .ExecuteAsync();
        }
        catch (MsalException) { }

        string accessToken = result.AccessToken;
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
    }

    public Task DisposeAsync() => Task.CompletedTask;
}

テスト クラスでの使用

次の例は、Microsoft Graph を呼び出すテストです。 このテストを、独自のアプリケーションまたは API でテストする内容に置き換えます。

public class ApiTests : IClassFixture<ClientFixture>
{
    ClientFixture clientFixture;

    public ApiTests(ClientFixture clientFixture)
    {
        this.clientFixture = clientFixture;
    }

    [Fact]
    public async Task GetRequestTest()
    {
        var testClient = clientFixture.httpClient;
        HttpResponseMessage response = await testClient.GetAsync("https://graph.microsoft.com/v1.0/me");
        var responseCode = response.StatusCode.ToString();
        Assert.Equal("OK", responseCode);
    }
}