MSAL ノードでの証明書資格情報の使用

MSAL Node (Web アプリ、デーモン アプリなど) を使用して、機密クライアント アプリケーションを構築できます。 機密 クライアントには、クライアント資格情報 が必須です。

Prerequisites

MSAL Node (Web アプリ、デーモン アプリなど) を使用して、機密クライアント アプリケーションを構築できます。 機密 クライアントには、クライアント資格情報 が必須です。 クライアント資格情報は次のようになります。

  • managed identity: これは証明書なしのシナリオであり、Azure インフラストラクチャを介して信頼が確立されます。 シークレット/証明書の管理は必要ありません。 MSAL はまだこの機能を実装していませんが、代わりに Azure Identity SDK を使用できます。 Azure リソースのマネージド ID に関するドキュメントを参照してください
  • clientSecret: アプリの登録中に生成されたシークレット文字列、または既存のアプリケーションの登録後に更新されたシークレット文字列。 運用環境では、これはお勧めしません。
  • clientCertificate: アプリの登録中に設定された証明書。 MSAL によって生成される アサーション の署名に使用されるため、証明書には秘密キーが必要です。 thumbprintSha256は証明書の X.509 SHA-256 拇印であり、privateKeyは PEM でエンコードされた秘密キーです。
  • clientAssertion: MSAL に アサーションを作成させる代わりに、アプリ開発者が制御します。 アサーションに追加の要求を追加する場合や、ローカル証明書ではなく署名に KeyVault を使用する場合に便利です。 アサーションの署名に使用する証明書は、アプリ登録時に引き続き設定する必要があります。

注: 1p アプリも x5cを送信する必要があります。 これは、サブジェクト名/発行者認証シナリオで使用される X.509 証明書チェーンです。

シークレットと証明書を安全に使用する

シークレットはハードコーディングしないでください。 dotenv npm パッケージを使用すると、シークレットの誤アップロードを防ぐために、.gitignore に含める必要がある .env ファイル (プロジェクトのルート ディレクトリにある) にシークレットまたは証明書を格納できます。

証明書は、NodeJS の fs モジュールを介してファイルから読み取ることもできます。 ただし、プロジェクトのディレクトリに保存しないでください。 運用アプリでは、Azure KeyVault またはその他のセキュリティで保護されたキー コンテナーから証明書をフェッチする必要があります。

詳細については、 証明書とシークレットを 参照してください。

MSAL サンプルを参照してください。 auth-code-with-certs

証明書の登録

証明書がない場合は、PowerShell または keyVault を使用して自己署名証明書Azure作成できます。

証明書をMicrosoft Entra IDにアップロードする必要があります。

  1. Azure ポータルに移動し、Microsoft Entra アプリの登録を選択します。
  2. 左側の [証明書とシークレット ] ブレードを選択します。
  3. [証明書の アップロード ] をクリックし、アップロードする証明書ファイル (例: example.crt) を選択します。
  4. 追加をクリックします。 証明書がアップロードされると、 拇印 (SHA-256)開始日および有効期限 の値が表示されます。

詳細については、「証明書をMicrosoft ID プラットフォームに登録する」を参照してください。

証明書を使用した MSAL ノードの初期化

const msal = require('@azure/msal-node');
require('dotenv').config(); // process.env now has the values defined in a .env file

const config = {
    auth: {
        clientId: "YOUR_CLIENT_ID",
        authority: "https://login.microsoftonline.com/YOUR_TENANT_ID",
        clientCertificate: {
            thumbprintSha256: process.env.thumbprint,
            privateKey: process.env.privateKey,
        }
    }
};

// Create msal application object
const cca = new msal.ConfidentialClientApplication(config);

thumbprintSha256privateKeyの両方が文字列である必要があります。 privateKey は、さらに次の形式である必要があります (PKCS#8)。

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDkpKPrsfpIijS3
z2HCpDsa7dxOsKIrm7F1AtGBjyB0yVDjlh/FA7jT5sd2ypBh3FVsZGJudQsLRKfE
// ...
-----END ENCRYPTED PRIVATE KEY-----

Note

または、秘密キーは、 -----BEGIN PRIVATE KEY----- (暗号化されていない PKCS#8) または -----BEGIN RSA PRIVATE KEY----- (PKCS#1) で始まる場合があります。 これらの形式も許容されます。 互換性のあるキーを PKCS#8 キー型に変換するには、次のコマンドを使用できます。

openssl pkcs8 -topk8 -inform PEM -outform PEM -in example.key -out example.key

秘密キーをパススルーで暗号化した場合 (または秘密キーが既に暗号化されている場合)、MSAL ノードに渡す前に暗号化を解除する必要があります。

重要: パスワードをソース コードにハードコーディングしないでください。 証明書の秘密キーと省略可能な descryption パスワードの両方を安全な場所 (例: Azure KeyVault) からフェッチし、Web API を使用して安全にデプロイする必要があります。

これは、Node の 暗号化モジュールを使用して行うことができます。 キーを解析してエクスポートするには、 createPrivateKey() メソッドを使用します。

const fs = require('fs');
const crypto = require('crypto');

const privateKeySource = fs.readFileSync('<path_to_key>/example.key')

const privateKeyObject = crypto.createPrivateKey({
    key: privateKeySource,
    passphrase: process.env.YOUR_PASSPHRASE,
    format: 'pem'
});

const privateKey = privateKeyObject.export({
    format: 'pem',
    type: 'pkcs8'
});

(省略可能)pfx から pem への変換

OpenSSL は、 pfx でエンコードされた証明書ファイルを pem に変換するために使用できます。

    openssl pkcs12 -in certificate.pfx -out certificate.pem

変換をプログラムで行う必要がある場合は、サード パーティのパッケージに依存する必要がある場合があります。Node.js にはネイティブメソッドが用意されていないためです。 たとえば、 node-forge のような一般的な TLS 実装を使用すると、次のことができます。

const forge = require('node-forge');

/**
 * @param {string} pfx: certificate + private key combination in pfx format
 * @param {string} passphrase: passphrase used to encrypt pfx file
 * @returns {Object}
 */
function convertPFX(pfx, passphrase = null) {

    const asn = forge.asn1.fromDer(forge.util.decode64(pfx));
    const p12 = forge.pkcs12.pkcs12FromAsn1(asn, true, passphrase);

    // Retrieve key data
    const keyData = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag })[forge.pki.oids.pkcs8ShroudedKeyBag]
        .concat(p12.getBags({ bagType: forge.pki.oids.keyBag })[forge.pki.oids.keyBag]);

    // Retrieve certificate data
    const certBags = p12.getBags({ bagType: forge.pki.oids.certBag })[forge.pki.oids.certBag];
    const certificate = forge.pki.certificateToPem(certBags[0].cert)

    // Convert a Forge private key to an ASN.1 RSAPrivateKey
    const rsaPrivateKey = forge.pki.privateKeyToAsn1(keyData[0].key);

    // Wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo
    const privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);

    // Convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
    const privateKey = forge.pki.privateKeyInfoToPem(privateKeyInfo);

    console.log("Converted certificate: \n", certificate);
    console.log("Converted key: \n", privateKey);

    return {
        certificate: certificate,
        key: privateKey
    };
}

(省略可能)HTTPS サーバーの作成

OAuth 2.0 プロトコルでは、可能な限り HTTPS 接続を使用することをお勧めします。 Azure App Serviceなどのほとんどのクラウド サービスでは、既定でプロキシ経由で HTTPS 接続が提供されます。 テスト目的で独自の HTTPS サーバーをセットアップする場合は、HTTPS サーバーの作成に関するガイダンス Node.js ドキュメントを参照してください。

ブラウザーのセキュリティ ポリシーをバイパスするには、OS の資格情報マネージャー / キー チェーンに自己署名証明書を追加する必要もあります。 その後もブラウザーに警告が表示されることがあります (Chrome など)。

  • Windowsユーザーの場合は、「方法: MMC スナップインで証明書を表示する」のガイドに従ってください。

  • Linux および MacOS ユーザーの場合は、証明書のインストール方法に関するオペレーティング システムのドキュメントを参照してください。

Warning

上記のコマンドを実行するには、 管理者 特権が必要な場合があります。

一般的な問題

場合によっては、AADSTS700027: Client assertion contains an invalid signature エラーなど、証明書を使用して認証しようとすると、Microsoft Entra IDからエラーが発生し、MSAL ノードの初期化に使用する証明書や秘密キーの形式が正しくないことを示す場合があります。 よくある理由は、MSAL Node に渡している証明書 / 秘密キーの文字列に、キャリッジ リターン\r)や 改行\n)などの予期しない文字が含まれていることです:

-----BEGIN CERTIFICATE-----\nMIIDDzCCAfegAwIBAgIJAMkyzQVK88NHMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYDVQQGEwJTRTESMBAGA1UECBMJU3RvY2tob2xtMQ4wDAYDVQQHEwVLaXN0YTEQMA4G0fbkqbKulrchGbNgkankZtEVg4PGjobZq7B+njvcVa7SsWF/WLq5AUbw==\r\n-----END CERTIFICATE-----

または、証明書/キー ファイルに バッグ属性が含まれている場合があります。

Bag Attributes
    localKeyID: 28 B5 8E 16 11 88 E9 00 58 D5 76 30 12 B9 59 B8 E4 CE 7C AA
subject=/C=UK/ST=Suffolk/L=Ipswich/O=Example plc/CN=alice
issuer=/C=UK/ST=Suffolk/L=Ipswich/O=Example plc/CN=Certificate Authority/emailAddress=ca@example.com\n
-----BEGIN CERTIFICATE-----
MIIDDzCCAfegAwIBAgIJAMkyzQVK88NHMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD
VQQGEwJTRTESMBAGA1UECBMJU3RvY2tob2xtMQ4wDAYDVQQHEwVLaXN0YTEQMA4G
0fbkqbKulrchGbNgkankZtEVg4PGjo+Y8MdMjtfSZB29hwYvfMX09jzJ68ZqmpYQ
njvcVtLbEZN5OGCkaslb/f2OxLbsUNgIbws538WnaaufDvKmQe2kUdWmpl9Wn9Bf
bZq7B+njvcVa7SsWF/WLq5AUbw==
-----END CERTIFICATE-----

このような場合は、MSAL ノード構成に渡す前に、文字列をクリーニングする必要があります。 次に例を示します。

const msal = require('@azure/msal-node');
const fs = require('fs');

const privateKeySource = fs.readFileSync('<path_to_key>/certs/example.key');
const privateKey = Buffer.from(privateKeySource, 'base64').toString().replace(/\r/g, "").replace(/\n/g, "");

const config = {
    auth: {
        clientId: "YOUR_CLIENT_ID",
        authority: "https://login.microsoftonline.com/YOUR_TENANT_ID",
        clientCertificate: {
            thumbprintSha256: process.env.thumbprint,
            privateKey: privateKey,
        }
    }
};

// Create msal application object
const cca = new msal.ConfidentialClientApplication(config);

こちらも参照ください