JavaScript 用 Azure ID ライブラリを使用した認証のベスト プラクティス

この記事では、Azure サービスに対する認証時に JavaScript アプリと TypeScript アプリのパフォーマンスと信頼性を最大限に高めるのに役立つガイドラインを提供します。 JavaScript 用の Azure Identity ライブラリを最大限に活用するには、潜在的な問題と軽減手法を理解することが重要です。

運用環境で確定的な資格情報を使用する

DefaultAzureCredential は、Azure Identity ライブラリを使い始めるための最も簡単な方法ですが、その便利さには一定のトレードオフも伴います。 最も顕著な点は、チェーン内の特定の資格情報が要求の認証に成功して使用されるかどうかを事前に保証できないことです。 運用環境では、この予測不可能性によって重大な問題や、ときには微妙な問題が発生する可能性があります。

たとえば、次のような一連の仮想的なイベントについて考えてみましょう。

  1. 組織のセキュリティ チームは、すべてのアプリでマネージド ID を使用して Azure リソースを認証することを義務付けています。
  2. 数か月間、Azure 仮想マシン (VM) でホストされている JavaScript アプリは、マネージド ID を介して認証するために DefaultAzureCredential を正常に使用します。
  3. 開発者はサポート チームに通知せずに、その VM に Azure CLI をインストールし、az login コマンドを実行して Azure を認証しました。
  4. Azure 環境でのこの新しい個別の構成変更により、元のマネージド ID を介した認証が予期せずサイレントで失敗し始めます。
  5. DefaultAzureCredential は失敗した ManagedIdentityCredential をスキップし、次に使用可能な資格情報 (AzureCliCredential) を検索します。
  6. アプリケーションでは、マネージド ID ではなくAzure CLI資格情報の使用が開始され、失敗したり、予期しない昇格や特権の低下が発生したりする可能性があります。

運用環境のアプリでこのような種類の微妙な問題やサイレント エラーが発生するのを防ぐには、DefaultAzureCredential を特定の TokenCredential 実装 (ManagedIdentityCredentialなど) に置き換えます。 使用可能な資格情報については、 Azure ID クライアント ライブラリのドキュメント を参照してください。

たとえば、Express.js プロジェクトで次の DefaultAzureCredential 構成を考えてみましょう。

import { DefaultAzureCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
import { BlobServiceClient } from "@azure/storage-blob";

const credential = new DefaultAzureCredential();

const secretClient = new SecretClient("https://keyVaultName.vault.azure.net", credential);
const blobServiceClient = new BlobServiceClient(
  "https://storageAccountName.blob.core.windows.net",
  credential
);

上記のコードを変更して、アプリが実行されている環境に基づいて資格情報を選択します。

import { AzureDeveloperCliCredential, ManagedIdentityCredential, ChainedTokenCredential,
         AzureCliCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
import { BlobServiceClient } from "@azure/storage-blob";

let credential;

// In production, use only ManagedIdentityCredential
if (process.env.NODE_ENV === 'production') {
  // For user-assigned managed identity, provide the client ID
  credential = new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID);
}
// In development, use a chain of credentials appropriate for local work
else {
  credential = new ChainedTokenCredential(
    new AzureCliCredential(),
    new AzureDeveloperCliCredential()
  );
}

// Initialize Key Vault client
const secretClient = new SecretClient("https://keyVaultName.vault.azure.net", credential);

// Initialize Blob Storage client
const blobServiceClient = new BlobServiceClient(
  "https://storageAccountName.blob.core.windows.net",
  credential
);

この例では、運用環境では ManagedIdentityCredential のみが使用されます。 その後、ローカル開発環境の認証ニーズは、else 句で定義されている資格情報のシーケンスによって処理されます。

資格情報インスタンスを再利用する

可能な場合は資格情報インスタンスを再利用して、アプリの回復力を向上させ、Microsoft Entra ID に発行されるアクセス トークン要求の数を減らしてください。 資格情報の再利用時には、基になる MSAL 依存関係によって管理されるアプリ トークン キャッシュからのトークンの取得が試行されます。 詳細については、「Azure Identity クライアント ライブラリの トークン キャッシュ」を参照してください。

トークン キャッシュの動作は、ブラウザー環境と Node.js 環境によって異なります。 Node.js アプリケーションでは、トークンは既定でメモリにキャッシュされます。つまり、アプリケーションの再起動時にキャッシュが失われます。 ブラウザー アプリケーションでは、認証フローと構成に応じて、トークンをブラウザー ストレージ (localStorage または sessionStorage) に保持できます。 これらの違いを理解することは、さまざまなアプリケーションの種類に対して資格情報の再利用戦略を実装する際に重要です。

Von Bedeutung

資格情報を再利用しない高トラフィックのアプリでは、Microsoft Entra ID から HTTP 429 のスロットリング応答が返される場合があり、その結果、アプリの停止につながる可能性があります。

推奨される資格情報の再利用戦略は、アプリケーション フレームワークによって異なります。

JavaScript アプリケーションで資格情報の再利用を実装するには、1 つの資格情報インスタンスを作成し、すべてのクライアント オブジェクトで再利用します。

import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
import { BlobServiceClient } from "@azure/storage-blob";

// Create a single credential instance
const credential = process.env.NODE_ENV === 'production'
  ? new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID)
  : new DefaultAzureCredential();

// Reuse the credential across different client objects
const secretClient = new SecretClient("https://keyVaultName.vault.azure.net", credential);
const blobServiceClient = new BlobServiceClient(
  "https://storageAccountName.blob.core.windows.net",
  credential
);

Express.js アプリケーションでは、資格情報をアプリ設定に格納し、ルート ハンドラーでアクセスできます。

import express from "express";
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
import { BlobServiceClient } from "@azure/storage-blob";

const app = express();

// Create a single credential instance at app startup
app.locals.credential = process.env.NODE_ENV === 'production'
  ? new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID)
  : new DefaultAzureCredential();

// Reuse the credential in route handlers
app.get('/api/secrets/:secretName', async (req, res) => {
  const secretClient = new SecretClient(
    "https://keyVaultName.vault.azure.net",
    req.app.locals.credential
  );

  try {
    const secret = await secretClient.getSecret(req.params.secretName);
    res.json({ name: secret.name, value: secret.value });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Add this route to the existing Express app
app.get('/api/blobs/:containerName', async (req, res) => {
  const blobServiceClient = new BlobServiceClient(
    "https://storageAccountName.blob.core.windows.net",
    req.app.locals.credential
  );

  try {
    // Get reference to a container
    const containerClient = blobServiceClient.getContainerClient(req.params.containerName);

    // List all blobs in the container
    const blobs = [];
    for await (const blob of containerClient.listBlobsFlat()) {
      blobs.push({
        name: blob.name,
        contentType: blob.properties.contentType,
        size: blob.properties.contentLength,
        lastModified: blob.properties.lastModified
      });
    }

    res.json({ containerName: req.params.containerName, blobs });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

トークンの有効期間とキャッシュ ロジックが必要なタイミングを理解する

Azure SDK クライアント ライブラリのコンテキスト外で Azure ID ライブラリの資格情報を使用する場合は、アプリで トークンの有効期間 とキャッシュ動作を管理する必要があります。

トークンの更新を試行できるタイミングに関するヒントをコンシューマーに提供する refreshAfterTimestamp の プロパティは、Azure Core ライブラリに依存する Azure SDK クライアント ライブラリによってトークンを更新するために自動的に使用されます。 トークン キャッシュをサポートする Azure ID ライブラリ資格情報を直接使用する場合、基になる MSAL キャッシュは、 refreshAfterTimestamp 時間が発生したときに自動的に事前に更新されます。 この設計により、クライアント コードはトークンが必要になるたびに TokenCredential.getToken() を 呼び出し、更新をライブラリに委任できます。

必要な場合にのみ TokenCredential.getToken() を呼び出すには、refreshAfterTimestamp の日付を確認し、その後に事前にトークンの更新を試みます。 具体的な実装はお客様が行います。

マネージド ID の再試行戦略を理解する

JavaScript 用の Azure Identity ライブラリを使用すると、 ManagedIdentityCredentialを使用してマネージド ID を使用して認証できます。 ManagedIdentityCredentialを使用する方法は、適用された再試行戦略に影響します。

  • DefaultAzureCredential経由で使用した場合、最初のトークン取得の試行が失敗した場合や、短時間でタイムアウトになった場合、再試行は試行されません。 これは、効率的な開発内部ループのために "高速に失敗" するように最適化されているため、最も回復力の低いオプションです。
  • 他のアプローチ、例えば ChainedTokenCredential や ManagedIdentityCredential を直接使用する場合など:
    • 再試行の間隔は 0.8 秒から開始し、既定では最大 5 回の再試行が試行されます。 このオプションは回復性のために最適化されていますが、開発内部ループで望ましくない可能性のある遅延が発生します。
    • 既定の再試行設定のいずれかを変更するには、options パラメーターで retryOptions プロパティを使用します。 たとえば、以下では最大 3 回再試行し、開始間隔を 0.5 秒としています。
import { ManagedIdentityCredential } from "@azure/identity";

const credential = new ManagedIdentityCredential(
  process.env.AZURE_CLIENT_ID, // For user-assigned managed identity
  {
    retryOptions: {
      maxRetries: 3,           // Maximum number of retry attempts
      retryDelayInMs: 500,     // Initial delay between retries (in milliseconds)
      maxRetryDelayInMs: 5000  // Maximum delay between retries (in milliseconds)
    }
  }
);

マネージド ID の再試行ポリシーのカスタマイズの詳細については、 TokenCredentialOptions から拡張される次のいずれかのオプションを参照してください。