機密クライアント アサーション

機密クライアント アプリケーションは、ID を証明するために、シークレットをMicrosoft Entra IDと交換します。 シークレットは次のようになります。

  • クライアント シークレット (アプリケーション パスワード)。
  • 証明書。標準の要求を含む署名付きアサーションの構築に使用されます。

このシークレットは直接署名されたアサーションの場合もあります。

MSAL.NET には、機密クライアント アプリに資格情報またはアサーションを提供する 4 つの方法があります。

  • .WithClientSecret()
  • .WithCertificate()
  • .WithClientAssertion()
  • .WithClientClaims()

Note

WithClientAssertion() API を使用して機密クライアントのトークンを取得することは可能ですが、既定では使用しないことをお勧めします。これは、より高度であり、一般的ではない非常に具体的なシナリオを処理するように設計されているためです。 .WithCertificate() API を使用すると、MSAL.NET はこれを処理できます。 この API は、必要に応じて認証要求をカスタマイズする機能を提供しますが、ほとんどの認証シナリオでは、 .WithCertificate() によって作成された既定のアサーションで十分です。 この API は、MSAL.NET が内部的に署名操作を実行できない場合の回避策としても使用できます。 2 つの違いは、WithCertificate()を使用するには、アサーションを作成するコンピューターで証明書と秘密キーを使用できるようにする必要があり、WithClientAssertion()を使用すると、Azure Key Vault内やマネージド ID から、またはハードウェア セキュリティ モジュールを使用して、他の場所でアサーションを計算できます。

クライアント アサーション

これは、証明書を自分で処理する場合に便利です。 たとえば、署名Azure KeyVault の API を使用する場合、証明書をダウンロードする必要がなくなります。 署名付きクライアント アサーションは、base64 でエンコードされた Microsoft Entra ID で義務付けられている必要な認証要求を含むペイロードを含む署名付き JWT の形式をとります。 また、"フェデレーション ID 資格情報" シナリオでは、別の ID プロバイダーの JWT を使用することもできます。

デリゲートを使用すると、MSAL が ID プロバイダーから新しいトークンを取得する必要があるたびにアサーションを計算できます。 キャッシュにトークンが見つかった場合、MSAL はデリゲートを呼び出しません。

string signedClientAssertion = GetOrComputeAssertion();
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                                          .WithClientAssertion(async (AssertionRequestOptions options) => {
                                            // use 'options.ClientID' or 'options.TokenEndpoint' to generate client assertion
                                            return await GetClientAssertionAsync(options.ClientID, options.TokenEndpoint, options.CancellationToken); 
                                          })
                                          .Build();

署名付きアサーションのMicrosoft Entra ID が想定するクレームは次のとおりです。

要求の種類 価値 説明
aud https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token "aud" (対象ユーザー) 要求は、JWT が意図されている受信者を識別します (こちら Microsoft Entra ID) RFC 7519、セクション 4.1.3 を参照してください。 この場合、その受信者は ID プロバイダーのトークン エンドポイントです
経験値 1601519414 "exp" (有効期限) 要求は、JWT の処理を受け入れることができなくなる時刻を指定します。 RFC 7519、セクション 4.1.4 を参照してください。 これにより、その時点まではアサーションを使用できるため、nbf の後、長くても 5~10 分以内の短い時間にしてください。 Microsoft Entra IDでは、現在exp時間に制限はありません。
iss {ClientID} "iss" (発行者) 要求は、JWT を発行したプリンシパル (この場合はクライアント アプリケーション) を識別します。 GUID アプリケーション ID を使用します。
jti (1つの Guid) "jti" (JWT ID) 要求は、JWT の一意の識別子を提供します。 識別子の値は、同じ値が誤って別のデータ オブジェクトに割り当てられる可能性がごくわずかであることを保証する方法で割り当てる必要があります。 アプリケーションで複数の発行者を使用する場合は、異なる発行者によって生成された値間でも競合を防ぐ必要があります。 "jti" 値は、大文字と小文字を区別する文字列です。 RFC 7519、セクション 4.1.7
nbf 1601519114 "nbf" (not before) クレームは、指定した時刻より前には JWT を処理に受け入れてはならないことを示します。 RFC 7519、セクション 4.1.5。 現在の時刻を使用することが適切です。
サブ {ClientID} "sub" (サブジェクト) 要求は JWT のサブジェクトを識別します。この場合は、アプリケーションも識別します。 issと同じ値を使用します。

証明書をクライアント シークレットとして使用する場合は、証明書を安全にデプロイする必要があります。 Windows上の証明書ストアやAzure Key Vaultを使用して、プラットフォームでサポートされているセキュリティで保護された場所に証明書を格納することをお勧めします。

アサーションの作成

これは、Microsoft.IdentityModel.JsonWebTokens を使用してアサーションを作成する例です。

        string GetSignedClientAssertion(X509Certificate2 certificate, string tenantId, string clientId)
        {                            
            // no need to add exp, nbf as JsonWebTokenHandler will add them by default.
            var claims = new Dictionary<string, object>()
            {
                { "aud", tokenEndpoint },
                { "iss", clientId },
                { "jti", Guid.NewGuid().ToString() },
                { "sub", clientId }
            };

            var securityTokenDescriptor = new SecurityTokenDescriptor
            {
                Claims = claims,
                SigningCredentials = new X509SigningCredentials(certificate)
            };

            var handler = new JsonWebTokenHandler();
            var signedClientAssertion = handler.CreateToken(securityTokenDescriptor);
        }

または、Microsoft.IdentityModel.JsonWebTokens を使用したくない場合:

static string Base64UrlEncode(byte[] arg)
{
    char Base64PadCharacter = '=';
    char Base64Character62 = '+';
    char Base64Character63 = '/';
    char Base64UrlCharacter62 = '-';
    char Base64UrlCharacter63 = '_';

    string s = Convert.ToBase64String(arg);
    s = s.Split(Base64PadCharacter)[0]; // RemoveAccount any trailing padding
    s = s.Replace(Base64Character62, Base64UrlCharacter62); // 62nd char of encoding
    s = s.Replace(Base64Character63, Base64UrlCharacter63); // 63rd char of encoding

    return s;
}

static string GetSignedClientAssertion(X509Certificate2 certificate, string tenantId, string clientId)
{
    // Get the RSA with the private key, used for signing.
    var rsa = certificate.GetRSAPrivateKey();

    //alg represents the desired signing algorithm, which is SHA-256 in this case
    //x5t represents the certificate thumbprint base64 url encoded
    var header = new Dictionary<string, string>()
    {
        { "alg", "PS256"},
        { "typ", "JWT" },
        { "x5t#S256", Base64UrlHelpers.Encode(certificate.GetCertHash(HashAlgorithmName.SHA256))},
    };

    //Please see the previous code snippet on how to craft claims for the GetClaims() method
    var claims = GetClaims(tenantId, clientId);

    var headerBytes = JsonSerializer.SerializeToUtf8Bytes(header);
    var claimsBytes = JsonSerializer.SerializeToUtf8Bytes(claims);
    string token = Base64UrlEncode(headerBytes) + "." + Base64UrlEncode(claimsBytes);

    string signature = Base64UrlEncode(rsa.SignData(Encoding.UTF8.GetBytes(token), HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
    string signedClientAssertion = string.Concat(token, ".", signature);
    return signedClientAssertion;
}

WithClientClaims

場合によっては、開発者はアサーションにいくつかの要求を挿入する必要がありますが、それでも MSAL でアサーションと署名の作成を処理したいと考えています。

WithClientClaims(X509Certificate2 certificate, IDictionary<string, string> claimsToSign, bool mergeWithDefaultClaims = true) は、Microsoft Entra ID で想定される要求に加えて、送信したい追加のクライアント要求を含む署名付きアサーションを生成します。

string ipAddress = "192.168.1.2";
X509Certificate2 certificate = ReadCertificate(config.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                                          .WithAuthority(new Uri(config.Authority))
                                          .WithClientClaims(certificate, 
                                                                      new Dictionary<string, string> { { "client_ip", ipAddress } })
                                          .Build();

渡すディクショナリ内のいずれかの要求が必須の要求の 1 つと同じ場合、追加の要求の値が考慮されます。 これは、MSAL.NET が計算したクレームを上書きします。

Microsoft Entra IDで想定される必須の要求を含め、独自の要求を指定する場合は、false パラメーターにmergeWithDefaultClaimsを渡します。