From 95ca36a1e728255b76eef0956620604a733bcba1 Mon Sep 17 00:00:00 2001
From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com>
Date: Wed, 14 Jan 2026 13:32:45 -0800
Subject: [PATCH 1/8] Add CertificateOptions and update the WithCertificate
APIs to use CertificateOptions
---
.../AppConfig/CertificateOptions.cs | 25 +++
.../ConfidentialClientApplicationBuilder.cs | 35 +++
...ntialClientApplicationBuilderExtensions.cs | 12 +-
.../PublicApi/net462/PublicAPI.Shipped.txt | 1 -
.../PublicApi/net462/PublicAPI.Unshipped.txt | 8 +-
.../PublicApi/net472/PublicAPI.Shipped.txt | 1 -
.../PublicApi/net472/PublicAPI.Unshipped.txt | 8 +-
.../net8.0-android/PublicAPI.Shipped.txt | 2 +-
.../net8.0-android/PublicAPI.Unshipped.txt | 8 +-
.../net8.0-ios/PublicAPI.Shipped.txt | 1 -
.../net8.0-ios/PublicAPI.Unshipped.txt | 8 +-
.../PublicApi/net8.0/PublicAPI.Shipped.txt | 1 -
.../PublicApi/net8.0/PublicAPI.Unshipped.txt | 8 +-
.../netstandard2.0/PublicAPI.Shipped.txt | 1 -
.../netstandard2.0/PublicAPI.Unshipped.txt | 8 +-
.../Core/Mocks/MockHttpManagerExtensions.cs | 27 ++-
...nfidentialClientApplicationBuilderTests.cs | 94 +++++++-
...lClientApplicationExtensibilityApiTests.cs | 77 ++++++-
...tialClientApplicationExtensibilityTests.cs | 201 +++++++++++++++++-
19 files changed, 491 insertions(+), 35 deletions(-)
create mode 100644 src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs b/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs
new file mode 100644
index 0000000000..be47d7860c
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.Identity.Client.AppConfig
+{
+ ///
+ /// Represents configuration options for certificate handling or management.
+ ///
+ public class CertificateOptions
+ {
+ ///
+ /// Gets or sets a value indicating whether the X.509 certificate chain (x5c) should be included in the token
+ /// request.
+ ///
+ /// Set this property to to include X5C in the token request;
+ /// otherwise, set it to .
+ public bool SendX5C { get; set; } = false;
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
index cf9e22b9c2..28f97f2912 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
@@ -130,6 +130,41 @@ public ConfidentialClientApplicationBuilder WithCertificate(X509Certificate2 cer
return this;
}
+ ///
+ /// Sets the certificate associated with the application.
+ /// Applicable to first-party applications only, this method also allows to specify
+ /// if the x5c claim should be sent to Azure AD.
+ /// Sending the x5c enables application developers to achieve easy certificate roll-over in Azure AD:
+ /// this method will send the certificate chain to Azure AD along with the token request,
+ /// so that Azure AD can use it to validate the subject name based on a trusted issuer policy.
+ /// This saves the application admin from the need to explicitly manage the certificate rollover
+ /// (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni
+ ///
+ /// The X509 certificate used as credentials to prove the identity of the application to Azure AD.
+ /// Configuration options for certificate handling.
+ /// You should use certificates with a private key size of at least 2048 bytes. Future versions of this library might reject certificates with smaller keys.
+ public ConfidentialClientApplicationBuilder WithCertificate(X509Certificate2 certificate, CertificateOptions certificateOptions)
+ {
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ if (certificateOptions?.SendX5C?? false && !certificate.HasPrivateKey)
+ {
+ throw new MsalClientException(MsalError.CertWithoutPrivateKey, MsalErrorMessage.CertMustHavePrivateKey(nameof(certificate)));
+ }
+
+ if (!certificate.HasPrivateKey)
+ {
+ throw new MsalClientException(MsalError.CertWithoutPrivateKey, MsalErrorMessage.CertMustHavePrivateKey(nameof(certificate)));
+ }
+
+ Config.ClientCredential = new CertificateClientCredential(certificate);
+ Config.SendX5C = certificateOptions?.SendX5C ?? false;
+ return this;
+ }
+
///
/// Sets the certificate associated with the application along with the specific claims to sign.
/// By default, this will merge the with the default required set of claims needed for authentication.
diff --git a/src/client/Microsoft.Identity.Client/Extensibility/ConfidentialClientApplicationBuilderExtensions.cs b/src/client/Microsoft.Identity.Client/Extensibility/ConfidentialClientApplicationBuilderExtensions.cs
index 2596563196..159bca7b34 100644
--- a/src/client/Microsoft.Identity.Client/Extensibility/ConfidentialClientApplicationBuilderExtensions.cs
+++ b/src/client/Microsoft.Identity.Client/Extensibility/ConfidentialClientApplicationBuilderExtensions.cs
@@ -4,6 +4,7 @@
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
+using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.Internal.ClientCredential;
namespace Microsoft.Identity.Client.Extensibility
@@ -41,8 +42,8 @@ public static ConfidentialClientApplicationBuilder WithAppTokenProvider(
///
/// An async callback that provides the certificate based on the application configuration.
/// Called before each network request to acquire a token.
- /// Must return a valid with a private key.
- ///
+ /// Must return a valid with a private key.
+ /// Configuration options for the certificate handling.
/// The builder to chain additional configuration calls.
/// Thrown when is null.
///
@@ -58,7 +59,8 @@ public static ConfidentialClientApplicationBuilder WithAppTokenProvider(
///
public static ConfidentialClientApplicationBuilder WithCertificate(
this ConfidentialClientApplicationBuilder builder,
- Func> certificateProvider)
+ Func> certificateProvider,
+ CertificateOptions certificateOptions)
{
if (certificateProvider == null)
{
@@ -69,7 +71,9 @@ public static ConfidentialClientApplicationBuilder WithCertificate(
// The certificate will be resolved dynamically via the provider in ResolveCertificateAsync
builder.Config.ClientCredential = new DynamicCertificateClientCredential(
certificateProvider: certificateProvider);
-
+
+ builder.Config.SendX5C = certificateOptions?.SendX5C ?? false;
+
return builder;
}
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Shipped.txt
index f0b1669e7f..d9760945d6 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Shipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Shipped.txt
@@ -1109,7 +1109,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft.
Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
-static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
index 7f5e2198da..e472e3f717 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
@@ -1,2 +1,8 @@
+Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
-static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
\ No newline at end of file
+static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Shipped.txt
index f0b1669e7f..d9760945d6 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Shipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Shipped.txt
@@ -1109,7 +1109,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft.
Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
-static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
index 7f5e2198da..e472e3f717 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
@@ -1,2 +1,8 @@
+Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
-static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
\ No newline at end of file
+static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt
index 2a0890082a..052e7123f4 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt
@@ -25,7 +25,7 @@ const Microsoft.Identity.Client.MsalError.BrokerResponseHashMismatch = "broker_r
const Microsoft.Identity.Client.MsalError.BrokerResponseReturnedError = "broker_response_returned_error" -> string
const Microsoft.Identity.Client.MsalError.CannotAccessUserInformationOrUserNotDomainJoined = "user_information_access_failed" -> string
const Microsoft.Identity.Client.MsalError.CannotInvokeBroker = "cannot_invoke_broker" -> string
-const Microsoft.Identity.Client.MsalError.CertificateNotRsa = "certificate_not_rsa" -> string
+const Microsoft.Identity.Client.MsalError.CertificateNotRsa = "certificaFwithcertificatete_not_rsa" -> string
const Microsoft.Identity.Client.MsalError.CertWithoutPrivateKey = "cert_without_private_key" -> string
const Microsoft.Identity.Client.MsalError.ClientCredentialAuthenticationTypeMustBeDefined = "Client_Credentials_Required_In_Confidential_Client_Application" -> string
const Microsoft.Identity.Client.MsalError.ClientCredentialAuthenticationTypesAreMutuallyExclusive = "Client_Credential_Authentication_Types_Are_Mutually_Exclusive" -> string
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
index 7f5e2198da..e472e3f717 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
@@ -1,2 +1,8 @@
+Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
-static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
\ No newline at end of file
+static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Shipped.txt
index c95127d057..979f38fe47 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Shipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Shipped.txt
@@ -1077,7 +1077,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft.
Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
-static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
index 7f5e2198da..e472e3f717 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
@@ -1,2 +1,8 @@
+Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
-static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
\ No newline at end of file
+static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Shipped.txt
index 0ff72f99cf..cd3e95cecc 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Shipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Shipped.txt
@@ -1071,7 +1071,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft.
Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
-static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
index 7f5e2198da..e472e3f717 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
@@ -1,2 +1,8 @@
+Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
-static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
\ No newline at end of file
+static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Shipped.txt
index 25abd7973d..fa93b701b7 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Shipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Shipped.txt
@@ -1071,7 +1071,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft.
Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
-static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
index 7f5e2198da..e472e3f717 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1,2 +1,8 @@
+Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
-static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
\ No newline at end of file
+static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs
index 8d4268f625..c1f8862c87 100644
--- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs
+++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs
@@ -189,8 +189,8 @@ public static MockHttpMessageHandler AddMockHandlerSuccessfulClientCredentialTok
string tokenType = "Bearer",
IList unexpectedHttpHeaders = null,
Dictionary expectedPostData = null,
- bool addClientInfo = false
- )
+ bool addClientInfo = false,
+ bool sendX5C = false)
{
var handler = new MockHttpMessageHandler()
{
@@ -201,6 +201,29 @@ public static MockHttpMessageHandler AddMockHandlerSuccessfulClientCredentialTok
ExpectedPostData = expectedPostData
};
+ if (sendX5C)
+ {
+ handler.AdditionalRequestValidation = (request) =>
+ {
+ // validate that the request have x5c header
+ if (expectedPostData != null && expectedPostData.ContainsKey("client_assertion"))
+ {
+ string clientAssertion = expectedPostData["client_assertion"];
+ string[] assertionParts = clientAssertion.Split('.');
+ if (assertionParts.Length != 3)
+ {
+ throw new InvalidDataException("client_assertion is not in the correct format");
+ }
+ string header = assertionParts[0];
+ string headerJson = Base64UrlHelpers.Decode(header);
+ if (!headerJson.Contains("\"x5c\""))
+ {
+ throw new InvalidDataException("client_assertion does not contain x5c header");
+ }
+ }
+ };
+ }
+
httpManager.AddMockHandler(handler);
return handler;
diff --git a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs
index 1bab2b0ab3..8c19bd4f25 100644
--- a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationBuilderTests.cs
@@ -2,16 +2,14 @@
// Licensed under the MIT License.
using System;
-using System.Drawing.Text;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
-using System.Security.Permissions;
-using System.Threading.Tasks;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.Internal;
-using Microsoft.Identity.Client.Internal.ClientCredential;
using Microsoft.Identity.Test.Common;
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -380,6 +378,94 @@ public void TestConstructor_WithCertificate_SendX5C()
Assert.IsTrue((app.AppConfig as ApplicationConfiguration).SendX5C);
}
+ [TestMethod]
+ [DeploymentItem(@"Resources\testCert.crtfile")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Internal.Analyzers", "IA5352:DoNotMisuseCryptographicApi", Justification = "Suppressing RoslynAnalyzers: Rule: IA5352 - Do Not Misuse Cryptographic APIs in test only code")]
+ public void TestConstructor_WithCertificate_CertificateOptions_SendX5C_True()
+ {
+ var cert = new X509Certificate2(
+ ResourceHelper.GetTestResourceRelativePath("testCert.crtfile"), TestConstants.TestCertPassword);
+ var certificateOptions = new CertificateOptions { SendX5C = true };
+
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithCertificate(cert, certificateOptions)
+ .Build();
+
+ Assert.IsTrue((app.AppConfig as ApplicationConfiguration).SendX5C, "SendX5C should be true when CertificateOptions.SendX5C is true");
+ }
+
+ [TestMethod]
+ [DeploymentItem(@"Resources\testCert.crtfile")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Internal.Analyzers", "IA5352:DoNotMisuseCryptographicApi", Justification = "Suppressing RoslynAnalyzers: Rule: IA5352 - Do Not Misuse Cryptographic APIs in test only code")]
+ public void TestConstructor_WithCertificate_CertificateOptions_SendX5C_False()
+ {
+ var cert = new X509Certificate2(
+ ResourceHelper.GetTestResourceRelativePath("testCert.crtfile"), TestConstants.TestCertPassword);
+ var certificateOptions = new CertificateOptions { SendX5C = false };
+
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithCertificate(cert, certificateOptions)
+ .Build();
+
+ Assert.IsFalse((app.AppConfig as ApplicationConfiguration).SendX5C, "SendX5C should be false when CertificateOptions.SendX5C is false");
+ }
+
+ [TestMethod]
+ [DeploymentItem(@"Resources\testCert.crtfile")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Internal.Analyzers", "IA5352:DoNotMisuseCryptographicApi", Justification = "Suppressing RoslynAnalyzers: Rule: IA5352 - Do Not Misuse Cryptographic APIs in test only code")]
+ public void TestConstructor_WithCertificate_NullCertificateOptions_DefaultsToSendX5C_False()
+ {
+ var cert = new X509Certificate2(
+ ResourceHelper.GetTestResourceRelativePath("testCert.crtfile"), TestConstants.TestCertPassword);
+
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithCertificate(cert, (CertificateOptions)null)
+ .Build();
+
+ Assert.IsFalse((app.AppConfig as ApplicationConfiguration).SendX5C, "SendX5C should default to false when CertificateOptions is null");
+ }
+
+ [TestMethod]
+ [DeploymentItem(@"Resources\testCert.crtfile")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Internal.Analyzers", "IA5352:DoNotMisuseCryptographicApi", Justification = "Suppressing RoslynAnalyzers: Rule: IA5352 - Do Not Misuse Cryptographic APIs in test only code")]
+ public void TestConstructor_WithCertificate_NullCertificate_ThrowsException()
+ {
+ var certificateOptions = new CertificateOptions { SendX5C = true };
+
+ Assert.ThrowsException(() =>
+ ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithCertificate((X509Certificate2)null, certificateOptions)
+ .Build());
+ }
+
+ [TestMethod]
+ [DeploymentItem(@"Resources\valid_cert.cer")]
+ public void TestConstructor_WithCertificate_CertificateOptions_WithoutPrivateKey()
+ {
+ var cert = new X509Certificate2(
+ ResourceHelper.GetTestResourceRelativePath("valid_cert.cer"));
+ var certificateOptions = new CertificateOptions { SendX5C = false };
+
+ try
+ {
+ ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithCertificate(cert, certificateOptions)
+ .Build();
+
+ Assert.Fail("Should have thrown MsalClientException");
+ }
+ catch (MsalClientException e)
+ {
+ Assert.IsNotNull(e);
+ Assert.AreEqual(MsalError.CertWithoutPrivateKey, e.ErrorCode);
+ }
+ }
+
[TestMethod]
[DeploymentItem(@"Resources\CustomInstanceMetadata.json")]
public void TestConstructor_WithValidInstanceDicoveryMetadata()
diff --git a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs
index eb8a299ae7..570dc5fc89 100644
--- a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs
@@ -5,6 +5,7 @@
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Client.Internal.ClientCredential;
using Microsoft.Identity.Test.Common;
@@ -17,6 +18,7 @@ namespace Microsoft.Identity.Test.Unit.AppConfigTests
public class ConfidentialClientApplicationExtensibilityApiTests
{
private X509Certificate2 _certificate;
+ private CertificateOptions _certificateOptions = new CertificateOptions();
[TestInitialize]
public void TestInitialize()
@@ -47,7 +49,7 @@ Task certificateProvider(AssertionRequestOptions options)
var app = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithExperimentalFeatures()
- .WithCertificate(certificateProvider)
+ .WithCertificate(certificateProvider, _certificateOptions)
.BuildConcrete();
// Assert
@@ -64,7 +66,7 @@ public void WithCertificate_ThrowsOnNullCallback()
ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithExperimentalFeatures()
- .WithCertificate((Func>)null)
+ .WithCertificate((Func>) null, null)
.Build());
Assert.AreEqual("certificateProvider", ex.ParamName);
@@ -93,8 +95,8 @@ Task secondProvider(AssertionRequestOptions options)
var app = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithExperimentalFeatures()
- .WithCertificate(firstProvider)
- .WithCertificate(secondProvider)
+ .WithCertificate(firstProvider, _certificateOptions)
+ .WithCertificate(secondProvider, _certificateOptions)
.BuildConcrete();
// Assert - last one should be stored
@@ -104,6 +106,65 @@ Task secondProvider(AssertionRequestOptions options)
Assert.IsInstanceOfType(config.ClientCredential, typeof(DynamicCertificateClientCredential));
}
+ [TestMethod]
+ public void WithCertificate_CertificateOptions_SendX5C_True_IsStored()
+ {
+ // Arrange
+ var certificateOptions = new CertificateOptions { SendX5C = true };
+ Task certificateProvider(AssertionRequestOptions options) => Task.FromResult(GetTestCertificate());
+
+ // Act
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithCertificate(certificateProvider, certificateOptions)
+ .BuildConcrete();
+
+ // Assert
+ var config = app.AppConfig as ApplicationConfiguration;
+ Assert.IsNotNull(config);
+ Assert.IsTrue(config.SendX5C, "SendX5C should be true when CertificateOptions.SendX5C is true");
+ }
+
+ [TestMethod]
+ public void WithCertificate_CertificateOptions_SendX5C_False_IsStored()
+ {
+ // Arrange
+ var certificateOptions = new CertificateOptions { SendX5C = false };
+ Task certificateProvider(AssertionRequestOptions options) => Task.FromResult(GetTestCertificate());
+
+ // Act
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithCertificate(certificateProvider, certificateOptions)
+ .BuildConcrete();
+
+ // Assert
+ var config = app.AppConfig as ApplicationConfiguration;
+ Assert.IsNotNull(config);
+ Assert.IsFalse(config.SendX5C, "SendX5C should be false when CertificateOptions.SendX5C is false");
+ }
+
+ [TestMethod]
+ public void WithCertificate_NullCertificateOptions_DefaultsToSendX5C_False()
+ {
+ // Arrange
+ Task certificateProvider(AssertionRequestOptions options) => Task.FromResult(GetTestCertificate());
+
+ // Act
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithCertificate(certificateProvider, null)
+ .BuildConcrete();
+
+ // Assert
+ var config = app.AppConfig as ApplicationConfiguration;
+ Assert.IsNotNull(config);
+ Assert.IsFalse(config.SendX5C, "SendX5C should default to false when CertificateOptions is null");
+ }
+
#endregion
#region OnMsalServiceFailure Tests
@@ -258,7 +319,7 @@ public void AllThreeExtensibilityPoints_CanBeConfiguredTogether()
var app = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithExperimentalFeatures()
- .WithCertificate(certificateProvider)
+ .WithCertificate(certificateProvider, _certificateOptions)
.OnMsalServiceFailure(onMsalServiceFailure)
.OnCompletion(onSuccess)
.BuildConcrete();
@@ -284,7 +345,7 @@ public void ExtensibilityPoints_CanBeConfiguredInAnyOrder()
.WithExperimentalFeatures()
.OnCompletion(onSuccess)
.OnMsalServiceFailure(onMsalServiceFailure)
- .WithCertificate(certificateProvider)
+ .WithCertificate(certificateProvider, _certificateOptions)
.BuildConcrete();
// Act - Order: OnMsalServiceFailure, Certificate, OnCompletion
@@ -292,7 +353,7 @@ public void ExtensibilityPoints_CanBeConfiguredInAnyOrder()
.Create(TestConstants.ClientId)
.WithExperimentalFeatures()
.OnMsalServiceFailure(onMsalServiceFailure)
- .WithCertificate(certificateProvider)
+ .WithCertificate(certificateProvider, _certificateOptions)
.OnCompletion(onSuccess)
.BuildConcrete();
@@ -325,7 +386,7 @@ Task certificateProvider(AssertionRequestOptions options)
.Create(TestConstants.ClientId)
.WithExperimentalFeatures()
.WithAuthority(TestConstants.AadAuthorityWithTestTenantId)
- .WithCertificate(certificateProvider)
+ .WithCertificate(certificateProvider, _certificateOptions)
.BuildConcrete();
// Assert
diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationExtensibilityTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationExtensibilityTests.cs
index 681fdcc758..dfd6a6bd85 100644
--- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationExtensibilityTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationExtensibilityTests.cs
@@ -6,6 +6,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.Identity.Test.Common.Core.Mocks;
@@ -17,12 +18,202 @@ namespace Microsoft.Identity.Test.Unit.PublicApiTests
[DeploymentItem(@"Resources\testCert.crtfile")]
public class ConfidentialClientApplicationExtensibilityTests : TestBase
{
+ private CertificateOptions _certificateOptions = new CertificateOptions();
+
[TestInitialize]
public override void TestInitialize()
{
base.TestInitialize();
}
+ #region CertificateOptions Tests
+
+ [TestMethod]
+ [Description("WithCertificate with CertificateOptions SendX5C=false stores options correctly")]
+ public void WithCertificate_CertificateOptions_SendX5C_False()
+ {
+ // Arrange
+ var certificate = CertHelper.GetOrCreateTestCert();
+ var certificateOptions = new CertificateOptions { SendX5C = false };
+
+ // Act
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithAuthority(TestConstants.AuthorityCommonTenant)
+ .WithCertificate((AssertionRequestOptions options) =>
+ {
+ return Task.FromResult(certificate);
+ }, certificateOptions)
+ .BuildConcrete();
+
+ // Assert
+ Assert.IsFalse((app.AppConfig as ApplicationConfiguration).SendX5C, "SendX5C should be false when CertificateOptions.SendX5C is false");
+ }
+
+ [TestMethod]
+ [Description("WithCertificate with CertificateOptions SendX5C=true stores options correctly")]
+ public void WithCertificate_CertificateOptions_SendX5C_True()
+ {
+ // Arrange
+ var certificate = CertHelper.GetOrCreateTestCert();
+ var certificateOptions = new CertificateOptions { SendX5C = true };
+
+ // Act
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithAuthority(TestConstants.AuthorityCommonTenant)
+ .WithCertificate((AssertionRequestOptions options) =>
+ {
+ return Task.FromResult(certificate);
+ }, certificateOptions)
+ .BuildConcrete();
+
+ // Assert
+ Assert.IsTrue((app.AppConfig as ApplicationConfiguration).SendX5C, "SendX5C should be true when CertificateOptions.SendX5C is true");
+ }
+
+ [TestMethod]
+ [Description("WithCertificate with null CertificateOptions defaults SendX5C to false")]
+ public void WithCertificate_NullCertificateOptions_DefaultsToFalse()
+ {
+ // Arrange
+ var certificate = CertHelper.GetOrCreateTestCert();
+
+ // Act
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithAuthority(TestConstants.AuthorityCommonTenant)
+ .WithCertificate((AssertionRequestOptions options) =>
+ {
+ return Task.FromResult(certificate);
+ }, null)
+ .BuildConcrete();
+
+ // Assert
+ Assert.IsFalse((app.AppConfig as ApplicationConfiguration).SendX5C, "SendX5C should default to false when CertificateOptions is null");
+ }
+
+ [TestMethod]
+ [Description("CertificateOptions with SendX5C=true works in full token acquisition flow")]
+ public async Task CertificateOptions_SendX5C_True_InTokenAcquisitionAsync()
+ {
+ // Arrange
+ using (var harness = CreateTestHarness())
+ {
+ harness.HttpManager.AddInstanceDiscoveryMockHandler();
+
+ var certificate = CertHelper.GetOrCreateTestCert();
+ var certificateOptions = new CertificateOptions { SendX5C = true };
+
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithAuthority(TestConstants.AuthorityCommonTenant)
+ .WithHttpManager(harness.HttpManager)
+ .WithCertificate((AssertionRequestOptions options) =>
+ {
+ return Task.FromResult(certificate);
+ }, certificateOptions)
+ .Build();
+
+ harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(sendX5C: true);
+
+ // Act
+ var result = await app.AcquireTokenForClient(TestConstants.s_scope)
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+
+ // Assert
+ Assert.IsNotNull(result.AccessToken);
+ Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
+ }
+ }
+
+ [TestMethod]
+ [Description("CertificateOptions with SendX5C=false works in full token acquisition flow")]
+ public async Task CertificateOptions_SendX5C_False_InTokenAcquisitionAsync()
+ {
+ // Arrange
+ using (var harness = CreateTestHarness())
+ {
+ harness.HttpManager.AddInstanceDiscoveryMockHandler();
+
+ var certificate = CertHelper.GetOrCreateTestCert();
+ var certificateOptions = new CertificateOptions { SendX5C = false };
+
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithAuthority(TestConstants.AuthorityCommonTenant)
+ .WithHttpManager(harness.HttpManager)
+ .WithCertificate((AssertionRequestOptions options) =>
+ {
+ return Task.FromResult(certificate);
+ }, certificateOptions)
+ .Build();
+
+ harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage();
+
+ // Act
+ var result = await app.AcquireTokenForClient(TestConstants.s_scope)
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+
+ // Assert
+ Assert.IsNotNull(result.AccessToken);
+ Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
+ }
+ }
+
+ [TestMethod]
+ [Description("CertificateOptions works with retry scenario")]
+ public async Task CertificateOptions_WorksWithRetryScenarioAsync()
+ {
+ // Arrange
+ using (var harness = CreateTestHarness())
+ {
+ harness.HttpManager.AddInstanceDiscoveryMockHandler();
+
+ var certificate = CertHelper.GetOrCreateTestCert();
+ var certificateOptions = new CertificateOptions { SendX5C = true };
+ int retryCount = 0;
+
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithAuthority(TestConstants.AuthorityCommonTenant)
+ .WithHttpManager(harness.HttpManager)
+ .WithCertificate((AssertionRequestOptions options) =>
+ {
+ return Task.FromResult(certificate);
+ }, certificateOptions)
+ .OnMsalServiceFailure((AssertionRequestOptions options, ExecutionResult result) =>
+ {
+ retryCount++;
+ return Task.FromResult(retryCount < 2);
+ })
+ .Build();
+
+ // Mock: fail once, then succeed
+ harness.HttpManager.AddFailureTokenEndpointResponse("request_failed");
+ harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(sendX5C: true);
+
+ // Act
+ var result = await app.AcquireTokenForClient(TestConstants.s_scope)
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+
+ // Assert
+ Assert.IsNotNull(result.AccessToken);
+ Assert.AreEqual(1, retryCount, "Should have retried once");
+ }
+ }
+
+ #endregion
+
#region WithCertificate (Dynamic Provider) Integration Tests
[TestMethod]
@@ -54,7 +245,7 @@ public async Task DynamicCertificateProvider_IsInvoked_AndUsedForAssertionAsync(
Assert.IsNotNull(options.TokenEndpoint);
return Task.FromResult(certificate);
- })
+ }, _certificateOptions)
.Build();
harness.HttpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage();
@@ -89,7 +280,7 @@ public async Task DynamicCertificateProvider_ReturnsNull_ThrowsExceptionAsync()
.WithCertificate((AssertionRequestOptions options) =>
{
return Task.FromResult(null); // Provider returns null
- })
+ }, _certificateOptions)
.Build();
// Act & Assert
@@ -224,7 +415,7 @@ public async Task OnMsalServiceFailure_NotInvokedForClientExceptionsAsync()
.WithCertificate((AssertionRequestOptions options) =>
{
return Task.FromResult(null); // Will cause MsalClientException
- })
+ }, _certificateOptions)
.OnMsalServiceFailure((AssertionRequestOptions options, ExecutionResult result) =>
{
callbackInvoked = true;
@@ -442,7 +633,7 @@ public async Task AllThreeExtensibilityPoints_WorkTogetherAsync()
Assert.AreEqual(TestConstants.ClientId, options.ClientID);
Assert.IsNotNull(options.TokenEndpoint, "TokenEndpoint should be available in cert provider");
return Task.FromResult(certificate);
- })
+ }, _certificateOptions)
.OnMsalServiceFailure((AssertionRequestOptions options, ExecutionResult result) =>
{
retryCallbackCount++;
@@ -508,7 +699,7 @@ public async Task CertificateRotation_DifferentCertOnRetryAsync()
certProviderCount++;
// Return different cert on retry
return Task.FromResult(certProviderCount == 1 ? cert1 : cert2);
- })
+ }, _certificateOptions)
.OnMsalServiceFailure((AssertionRequestOptions options, ExecutionResult result) =>
{
// Validate ExecutionResult
From 51a1cf35a781a6b16e5ffd14bfc1c38b5d607bc9 Mon Sep 17 00:00:00 2001
From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com>
Date: Wed, 14 Jan 2026 13:57:09 -0800
Subject: [PATCH 2/8] Update validation applied twice
---
.../AppConfig/ConfidentialClientApplicationBuilder.cs | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
index 28f97f2912..8d7de24387 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
@@ -150,11 +150,6 @@ public ConfidentialClientApplicationBuilder WithCertificate(X509Certificate2 cer
throw new ArgumentNullException(nameof(certificate));
}
- if (certificateOptions?.SendX5C?? false && !certificate.HasPrivateKey)
- {
- throw new MsalClientException(MsalError.CertWithoutPrivateKey, MsalErrorMessage.CertMustHavePrivateKey(nameof(certificate)));
- }
-
if (!certificate.HasPrivateKey)
{
throw new MsalClientException(MsalError.CertWithoutPrivateKey, MsalErrorMessage.CertMustHavePrivateKey(nameof(certificate)));
From 9f6faf3b3216c7899d33a458ace337b79973f05b Mon Sep 17 00:00:00 2001
From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com>
Date: Thu, 15 Jan 2026 09:38:38 -0800
Subject: [PATCH 3/8] Add AssociateTokensWithCertificateSerialNumber to options
---
.../AppConfig/CertificateOptions.cs | 17 +++--
.../ConfidentialClientApplicationBuilder.cs | 9 ++-
.../PublicApi/net462/PublicAPI.Unshipped.txt | 2 +
.../PublicApi/net472/PublicAPI.Unshipped.txt | 2 +
.../net8.0-android/PublicAPI.Unshipped.txt | 2 +
.../net8.0-ios/PublicAPI.Unshipped.txt | 2 +
.../PublicApi/net8.0/PublicAPI.Unshipped.txt | 2 +
.../netstandard2.0/PublicAPI.Unshipped.txt | 2 +
...lClientApplicationExtensibilityApiTests.cs | 69 +++++++++++++++++++
.../ClientCredentialWithCertTest.cs | 38 +++++++---
10 files changed, 129 insertions(+), 16 deletions(-)
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs b/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs
index be47d7860c..d1d76fd81f 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs
@@ -1,12 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
namespace Microsoft.Identity.Client.AppConfig
{
///
@@ -18,8 +12,17 @@ public class CertificateOptions
/// Gets or sets a value indicating whether the X.509 certificate chain (x5c) should be included in the token
/// request.
///
- /// Set this property to to include X5C in the token request;
+ /// Set this property to to include X5C in the token request
/// otherwise, set it to .
public bool SendX5C { get; set; } = false;
+
+ ///
+ /// Gets or sets a value indicating if the application tokens acquired from Azure AD are associated with the certificate serial number.
+ /// This property when set, allow you to associate the tokens acquired from Azure AD with the certificate serial number.
+ /// This can be used to partition the cache by certificate. Tokens acquired with one certificate will not be accessible to another certificate with a different serial number.
+ /// Set this property to to indicate that the tokens acquired from Azure AD are associated with the certificate serial number,
+ /// by default it is set to />
+ ///
+ public bool AssociateTokensWithCertificateSerialNumber { get; set; } = false;
}
}
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
index 8d7de24387..dd36ffc447 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
+using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -141,7 +142,7 @@ public ConfidentialClientApplicationBuilder WithCertificate(X509Certificate2 cer
/// (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni
///
/// The X509 certificate used as credentials to prove the identity of the application to Azure AD.
- /// Configuration options for certificate handling.
+ /// Configuration options for certificate handling. See for more information.
/// You should use certificates with a private key size of at least 2048 bytes. Future versions of this library might reject certificates with smaller keys.
public ConfidentialClientApplicationBuilder WithCertificate(X509Certificate2 certificate, CertificateOptions certificateOptions)
{
@@ -155,8 +156,14 @@ public ConfidentialClientApplicationBuilder WithCertificate(X509Certificate2 cer
throw new MsalClientException(MsalError.CertWithoutPrivateKey, MsalErrorMessage.CertMustHavePrivateKey(nameof(certificate)));
}
+ if (certificateOptions?.AssociateTokensWithCertificateSerialNumber ?? false)
+ {
+ Config.CertificateIdToAssociateWithToken = certificate.SerialNumber;
+ }
+
Config.ClientCredential = new CertificateClientCredential(certificate);
Config.SendX5C = certificateOptions?.SendX5C ?? false;
+
return this;
}
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
index e472e3f717..634e84e125 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
@@ -1,4 +1,6 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
index e472e3f717..634e84e125 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
@@ -1,4 +1,6 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
index e472e3f717..634e84e125 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
@@ -1,4 +1,6 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
index e472e3f717..634e84e125 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
@@ -1,4 +1,6 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
index e472e3f717..634e84e125 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
@@ -1,4 +1,6 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
index e472e3f717..634e84e125 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1,4 +1,6 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
diff --git a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs
index 570dc5fc89..7af1274840 100644
--- a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs
@@ -8,6 +8,7 @@
using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Client.Internal.ClientCredential;
+using Microsoft.Identity.Client.RP;
using Microsoft.Identity.Test.Common;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -165,6 +166,74 @@ public void WithCertificate_NullCertificateOptions_DefaultsToSendX5C_False()
Assert.IsFalse(config.SendX5C, "SendX5C should default to false when CertificateOptions is null");
}
+ [TestMethod]
+ public void WithCertificate_CertificateOptions_AssociateTokensWithCertificateSerialNumber_True_IsStored()
+ {
+ // Arrange
+ var certificateOptions = new CertificateOptions { AssociateTokensWithCertificateSerialNumber = true };
+ Task certificateProvider(AssertionRequestOptions options) => Task.FromResult(GetTestCertificate());
+
+ // Act
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithCertificate(certificateProvider, certificateOptions)
+ .BuildConcrete();
+
+ // Assert
+ var config = app.AppConfig as ApplicationConfiguration;
+ Assert.IsNotNull(config);
+ Assert.IsTrue(certificateOptions.AssociateTokensWithCertificateSerialNumber,
+ "CertificateOptions.AssociateTokensWithCertificateSerialNumber should be true");
+ }
+
+ [TestMethod]
+ public void WithCertificate_CertificateOptions_AssociateTokensWithCertificateSerialNumber_False_IsStored()
+ {
+ // Arrange
+ var certificateOptions = new CertificateOptions { AssociateTokensWithCertificateSerialNumber = false };
+ Task certificateProvider(AssertionRequestOptions options) => Task.FromResult(GetTestCertificate());
+
+ // Act
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithCertificate(certificateProvider, certificateOptions)
+ .BuildConcrete();
+
+ // Assert
+ var config = app.AppConfig as ApplicationConfiguration;
+ Assert.IsNotNull(config);
+ Assert.IsFalse(certificateOptions.AssociateTokensWithCertificateSerialNumber,
+ "CertificateOptions.AssociateTokensWithCertificateSerialNumber should be false");
+ }
+
+ [TestMethod]
+ public void WithCertificate_CertificateOptions_BothPropertiesSet_AreStored()
+ {
+ // Arrange
+ var certificateOptions = new CertificateOptions
+ {
+ SendX5C = true,
+ AssociateTokensWithCertificateSerialNumber = true
+ };
+ Task certificateProvider(AssertionRequestOptions options) => Task.FromResult(GetTestCertificate());
+
+ // Act
+ var app = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithExperimentalFeatures()
+ .WithCertificate(certificateProvider, certificateOptions)
+ .BuildConcrete();
+
+ // Assert
+ var config = app.AppConfig as ApplicationConfiguration;
+ Assert.IsNotNull(config);
+ Assert.IsTrue(config.SendX5C, "SendX5C should be true");
+ Assert.IsTrue(certificateOptions.AssociateTokensWithCertificateSerialNumber,
+ "AssociateTokensWithCertificateSerialNumber should be true");
+ }
+
#endregion
#region OnMsalServiceFailure Tests
diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
index 5e03718004..b2e845041b 100644
--- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
+++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
@@ -14,6 +14,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.PlatformsCommon.Shared;
@@ -856,20 +857,38 @@ public async Task RopcCcaSendsX5CUsingRequestLevelAPIAsync(bool sendX5C)
}
}
- [TestMethod]
- public async Task EnsureCertificateSerialNumberIsAddedToCacheKeyTestAsync()
+
+ [DataTestMethod]
+ [DataRow(true)]
+ [DataRow(false)]
+ public async Task EnsureCertificateSerialNumberIsAddedToCacheKeyTestAsync(bool useCertificateOptions)
{
using (var httpManager = new MockHttpManager())
{
var certificate = CertHelper.GetOrCreateTestCert();
- var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
+ var builder = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
.WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true)
.WithRedirectUri(TestConstants.RedirectUri)
- .WithCertificate(certificate, true, true)
.WithHttpManager(httpManager)
- .WithExperimentalFeatures()
- .BuildConcrete();
+ .WithExperimentalFeatures();
+
+ if (useCertificateOptions)
+ {
+ CertificateOptions certificateOptions = new CertificateOptions()
+ {
+ SendX5C = true,
+ AssociateTokensWithCertificateSerialNumber = true
+ };
+
+ builder.WithCertificate(certificate, certificateOptions);
+ }
+ else
+ {
+ builder.WithCertificate(certificate, true, true);
+ }
+
+ var app = builder.BuildConcrete();
app.AppTokenCache.SetBeforeAccess(BeforeCacheAccess);
app.AppTokenCache.SetAfterAccess(AfterCacheAccess);
@@ -956,8 +975,8 @@ public async Task EnsureCertificateSerialNumberIsAddedToCacheKeyTestAsync()
}
}
- [TestMethod]
- public async Task EnsureDefaultCacheKeyBehaviorWhenCertSerialNumberIsNotUsedTestAsync()
+ [DataTestMethod]
+ public async Task EnsureDefaultCacheKeyBehaviorWhenCertSerialNumberIsNotUsedTestAsync(bool useCertificateOptions)
{
using (var httpManager = new MockHttpManager())
{
@@ -972,6 +991,9 @@ public async Task EnsureDefaultCacheKeyBehaviorWhenCertSerialNumberIsNotUsedTest
.WithExperimentalFeatures()
.BuildConcrete();
+
+
+
app.AppTokenCache.SetBeforeAccess(BeforeCacheAccess);
app.AppTokenCache.SetAfterAccess(AfterCacheAccess);
From a2a4de0b914273d534222ff21954bf5ad3e7cf1f Mon Sep 17 00:00:00 2001
From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com>
Date: Thu, 15 Jan 2026 11:06:20 -0800
Subject: [PATCH 4/8] Fix typo
---
.../PublicApi/net8.0-android/PublicAPI.Shipped.txt | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt
index 052e7123f4..1f408bc861 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt
@@ -25,7 +25,7 @@ const Microsoft.Identity.Client.MsalError.BrokerResponseHashMismatch = "broker_r
const Microsoft.Identity.Client.MsalError.BrokerResponseReturnedError = "broker_response_returned_error" -> string
const Microsoft.Identity.Client.MsalError.CannotAccessUserInformationOrUserNotDomainJoined = "user_information_access_failed" -> string
const Microsoft.Identity.Client.MsalError.CannotInvokeBroker = "cannot_invoke_broker" -> string
-const Microsoft.Identity.Client.MsalError.CertificateNotRsa = "certificaFwithcertificatete_not_rsa" -> string
+const Microsoft.Identity.Client.MsalError.CertificateNotRsa = "certificate_not_rsa"
const Microsoft.Identity.Client.MsalError.CertWithoutPrivateKey = "cert_without_private_key" -> string
const Microsoft.Identity.Client.MsalError.ClientCredentialAuthenticationTypeMustBeDefined = "Client_Credentials_Required_In_Confidential_Client_Application" -> string
const Microsoft.Identity.Client.MsalError.ClientCredentialAuthenticationTypesAreMutuallyExclusive = "Client_Credential_Authentication_Types_Are_Mutually_Exclusive" -> string
@@ -1075,7 +1075,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft.
Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
-static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
From b12c1762a049c9ab890852ef04487f0c96eea92d Mon Sep 17 00:00:00 2001
From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com>
Date: Thu, 15 Jan 2026 11:37:00 -0800
Subject: [PATCH 5/8] Fix typo
---
.../PublicApi/net8.0-android/PublicAPI.Shipped.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt
index 1f408bc861..497e3876d1 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt
@@ -25,7 +25,7 @@ const Microsoft.Identity.Client.MsalError.BrokerResponseHashMismatch = "broker_r
const Microsoft.Identity.Client.MsalError.BrokerResponseReturnedError = "broker_response_returned_error" -> string
const Microsoft.Identity.Client.MsalError.CannotAccessUserInformationOrUserNotDomainJoined = "user_information_access_failed" -> string
const Microsoft.Identity.Client.MsalError.CannotInvokeBroker = "cannot_invoke_broker" -> string
-const Microsoft.Identity.Client.MsalError.CertificateNotRsa = "certificate_not_rsa"
+const Microsoft.Identity.Client.MsalError.CertificateNotRsa = "certificate_not_rsa" -> string
const Microsoft.Identity.Client.MsalError.CertWithoutPrivateKey = "cert_without_private_key" -> string
const Microsoft.Identity.Client.MsalError.ClientCredentialAuthenticationTypeMustBeDefined = "Client_Credentials_Required_In_Confidential_Client_Application" -> string
const Microsoft.Identity.Client.MsalError.ClientCredentialAuthenticationTypesAreMutuallyExclusive = "Client_Credential_Authentication_Types_Are_Mutually_Exclusive" -> string
From 083560fbbea1a837861c836a68313cb0afb862b6 Mon Sep 17 00:00:00 2001
From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com>
Date: Thu, 15 Jan 2026 11:56:33 -0800
Subject: [PATCH 6/8] Fix tests
---
.../ConfidentialClientApplicationBuilderExtensions.cs | 4 ++++
.../PublicApiTests/ClientCredentialWithCertTest.cs | 2 ++
2 files changed, 6 insertions(+)
diff --git a/src/client/Microsoft.Identity.Client/Extensibility/ConfidentialClientApplicationBuilderExtensions.cs b/src/client/Microsoft.Identity.Client/Extensibility/ConfidentialClientApplicationBuilderExtensions.cs
index 159bca7b34..05c150b079 100644
--- a/src/client/Microsoft.Identity.Client/Extensibility/ConfidentialClientApplicationBuilderExtensions.cs
+++ b/src/client/Microsoft.Identity.Client/Extensibility/ConfidentialClientApplicationBuilderExtensions.cs
@@ -62,6 +62,8 @@ public static ConfidentialClientApplicationBuilder WithCertificate(
Func> certificateProvider,
CertificateOptions certificateOptions)
{
+ builder.ValidateUseOfExperimentalFeature();
+
if (certificateProvider == null)
{
throw new ArgumentNullException(nameof(certificateProvider));
@@ -122,6 +124,8 @@ public static ConfidentialClientApplicationBuilder OnMsalServiceFailure(
this ConfidentialClientApplicationBuilder builder,
Func> onMsalServiceFailure)
{
+ builder.ValidateUseOfExperimentalFeature();
+
if (onMsalServiceFailure == null)
throw new ArgumentNullException(nameof(onMsalServiceFailure));
diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
index b2e845041b..0d377aefd7 100644
--- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
+++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
@@ -976,6 +976,8 @@ public async Task EnsureCertificateSerialNumberIsAddedToCacheKeyTestAsync(bool u
}
[DataTestMethod]
+ [DataRow(true)]
+ [DataRow(false)]
public async Task EnsureDefaultCacheKeyBehaviorWhenCertSerialNumberIsNotUsedTestAsync(bool useCertificateOptions)
{
using (var httpManager = new MockHttpManager())
From 9b12cb21cbb39d61d7a22da55e8f149b998d6486 Mon Sep 17 00:00:00 2001
From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:29:08 -0800
Subject: [PATCH 7/8] Address comments
---
.../AppConfig/CertificateOptions.cs | 6 +++---
.../ConfidentialClientApplicationBuilder.cs | 2 +-
.../IsExternalInit.cs | 11 +++++++++++
.../Microsoft.Identity.Client.csproj | 5 +++++
.../PublicApi/net462/PublicAPI.Unshipped.txt | 7 +++----
.../PublicApi/net472/PublicAPI.Unshipped.txt | 8 +++-----
.../net8.0-android/PublicAPI.Unshipped.txt | 7 +++----
.../net8.0-ios/PublicAPI.Unshipped.txt | 7 +++----
.../PublicApi/net8.0/PublicAPI.Unshipped.txt | 8 +++-----
.../netstandard2.0/PublicAPI.Unshipped.txt | 7 +++----
...alClientApplicationExtensibilityApiTests.cs | 18 +++++++++---------
.../ClientCredentialWithCertTest.cs | 2 +-
12 files changed, 48 insertions(+), 40 deletions(-)
create mode 100644 src/client/Microsoft.Identity.Client/IsExternalInit.cs
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs b/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs
index d1d76fd81f..0993225bca 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/CertificateOptions.cs
@@ -6,7 +6,7 @@ namespace Microsoft.Identity.Client.AppConfig
///
/// Represents configuration options for certificate handling or management.
///
- public class CertificateOptions
+ public record CertificateOptions
{
///
/// Gets or sets a value indicating whether the X.509 certificate chain (x5c) should be included in the token
@@ -14,7 +14,7 @@ public class CertificateOptions
///
/// Set this property to to include X5C in the token request
/// otherwise, set it to .
- public bool SendX5C { get; set; } = false;
+ public bool SendX5C { get; init; } = false;
///
/// Gets or sets a value indicating if the application tokens acquired from Azure AD are associated with the certificate serial number.
@@ -23,6 +23,6 @@ public class CertificateOptions
/// Set this property to to indicate that the tokens acquired from Azure AD are associated with the certificate serial number,
/// by default it is set to />
///
- public bool AssociateTokensWithCertificateSerialNumber { get; set; } = false;
+ public bool AssociateTokensWithCertificate { get; init; } = false;
}
}
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
index dd36ffc447..212fecbfe1 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs
@@ -156,7 +156,7 @@ public ConfidentialClientApplicationBuilder WithCertificate(X509Certificate2 cer
throw new MsalClientException(MsalError.CertWithoutPrivateKey, MsalErrorMessage.CertMustHavePrivateKey(nameof(certificate)));
}
- if (certificateOptions?.AssociateTokensWithCertificateSerialNumber ?? false)
+ if (certificateOptions?.AssociateTokensWithCertificate ?? false)
{
Config.CertificateIdToAssociateWithToken = certificate.SerialNumber;
}
diff --git a/src/client/Microsoft.Identity.Client/IsExternalInit.cs b/src/client/Microsoft.Identity.Client/IsExternalInit.cs
new file mode 100644
index 0000000000..ce2769ba9f
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/IsExternalInit.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#if NETSTANDARD || NET462 || NET472
+namespace System.Runtime.CompilerServices
+{
+ internal static class IsExternalInit
+ {
+ }
+}
+#endif
diff --git a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
index 7c08fe3762..e94f8735bb 100644
--- a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
+++ b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
@@ -80,6 +80,7 @@
+
@@ -162,4 +163,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
index a5874d495d..f3b0925c82 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
@@ -1,9 +1,8 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
-Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.init -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.init -> void
Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
index 4d31c27150..a76e8c6337 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
@@ -1,12 +1,10 @@
Microsoft.Identity.Client.ManagedIdentityPopExtensions
Microsoft.Identity.Client.AppConfig.CertificateOptions
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
-Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.init -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.init -> void
Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
-Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder.WithAttributes(string attributeJson) -> Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
index 82b0d21146..44f8580444 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
@@ -1,9 +1,8 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
-Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.init -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.init -> void
Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
index 82b0d21146..44f8580444 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
@@ -1,9 +1,8 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
-Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.init -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.init -> void
Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
index 46cc13cc25..44f8580444 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
@@ -1,11 +1,9 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
-Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.init -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.init -> void
Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
-Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
index 82b0d21146..44f8580444 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1,9 +1,8 @@
Microsoft.Identity.Client.AppConfig.CertificateOptions
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificateSerialNumber.set -> void
-Microsoft.Identity.Client.AppConfig.CertificateOptions.CertificateOptions() -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.get -> bool
+Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.init -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
-Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.set -> void
+Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.init -> void
Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Microsoft.Identity.Client.ManagedIdentityPopExtensions
diff --git a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs
index 7af1274840..e775df78d5 100644
--- a/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/AppConfigTests/ConfidentialClientApplicationExtensibilityApiTests.cs
@@ -170,7 +170,7 @@ public void WithCertificate_NullCertificateOptions_DefaultsToSendX5C_False()
public void WithCertificate_CertificateOptions_AssociateTokensWithCertificateSerialNumber_True_IsStored()
{
// Arrange
- var certificateOptions = new CertificateOptions { AssociateTokensWithCertificateSerialNumber = true };
+ var certificateOptions = new CertificateOptions { AssociateTokensWithCertificate = true };
Task certificateProvider(AssertionRequestOptions options) => Task.FromResult(GetTestCertificate());
// Act
@@ -183,15 +183,15 @@ public void WithCertificate_CertificateOptions_AssociateTokensWithCertificateSer
// Assert
var config = app.AppConfig as ApplicationConfiguration;
Assert.IsNotNull(config);
- Assert.IsTrue(certificateOptions.AssociateTokensWithCertificateSerialNumber,
- "CertificateOptions.AssociateTokensWithCertificateSerialNumber should be true");
+ Assert.IsTrue(certificateOptions.AssociateTokensWithCertificate,
+ "CertificateOptions.AssociateTokensWithCertificate should be true");
}
[TestMethod]
public void WithCertificate_CertificateOptions_AssociateTokensWithCertificateSerialNumber_False_IsStored()
{
// Arrange
- var certificateOptions = new CertificateOptions { AssociateTokensWithCertificateSerialNumber = false };
+ var certificateOptions = new CertificateOptions { AssociateTokensWithCertificate = false };
Task certificateProvider(AssertionRequestOptions options) => Task.FromResult(GetTestCertificate());
// Act
@@ -204,8 +204,8 @@ public void WithCertificate_CertificateOptions_AssociateTokensWithCertificateSer
// Assert
var config = app.AppConfig as ApplicationConfiguration;
Assert.IsNotNull(config);
- Assert.IsFalse(certificateOptions.AssociateTokensWithCertificateSerialNumber,
- "CertificateOptions.AssociateTokensWithCertificateSerialNumber should be false");
+ Assert.IsFalse(certificateOptions.AssociateTokensWithCertificate,
+ "CertificateOptions.AssociateTokensWithCertificate should be false");
}
[TestMethod]
@@ -215,7 +215,7 @@ public void WithCertificate_CertificateOptions_BothPropertiesSet_AreStored()
var certificateOptions = new CertificateOptions
{
SendX5C = true,
- AssociateTokensWithCertificateSerialNumber = true
+ AssociateTokensWithCertificate = true
};
Task certificateProvider(AssertionRequestOptions options) => Task.FromResult(GetTestCertificate());
@@ -230,8 +230,8 @@ public void WithCertificate_CertificateOptions_BothPropertiesSet_AreStored()
var config = app.AppConfig as ApplicationConfiguration;
Assert.IsNotNull(config);
Assert.IsTrue(config.SendX5C, "SendX5C should be true");
- Assert.IsTrue(certificateOptions.AssociateTokensWithCertificateSerialNumber,
- "AssociateTokensWithCertificateSerialNumber should be true");
+ Assert.IsTrue(certificateOptions.AssociateTokensWithCertificate,
+ "AssociateTokensWithCertificate should be true");
}
#endregion
diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
index 0d377aefd7..dd4954e5a1 100644
--- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
+++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
@@ -878,7 +878,7 @@ public async Task EnsureCertificateSerialNumberIsAddedToCacheKeyTestAsync(bool u
CertificateOptions certificateOptions = new CertificateOptions()
{
SendX5C = true,
- AssociateTokensWithCertificateSerialNumber = true
+ AssociateTokensWithCertificate = true
};
builder.WithCertificate(certificate, certificateOptions);
From 5e74da60caaf78a96ff3b6ce8d3e16dc80581898 Mon Sep 17 00:00:00 2001
From: Neha Bhargava <61847233+neha-bhargava@users.noreply.github.com>
Date: Tue, 27 Jan 2026 13:43:49 -0800
Subject: [PATCH 8/8] Undo changes to csproj
---
.../Microsoft.Identity.Client.csproj | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
index e94f8735bb..7c08fe3762 100644
--- a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
+++ b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj
@@ -80,7 +80,6 @@
-
@@ -163,8 +162,4 @@
-
-
-
-
\ No newline at end of file