diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs
index 3d45a15dae..896a5331d7 100644
--- a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs
+++ b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs
@@ -94,25 +94,17 @@ public AcquireTokenForClientParameterBuilder WithSendX5C(bool withSendX5C)
/// For more information, refer to the Proof-of-Possession documentation.
///
/// The current instance of to enable method chaining.
+ ///
+ /// The application must be configured with a certificate credential (via WithCertificate) or a client assertion
+ /// that provides a TokenBindingCertificate. Certificate validation and MTLS setup will be performed during token acquisition.
+ ///
public AcquireTokenForClientParameterBuilder WithMtlsProofOfPossession()
{
- if (ServiceBundle.Config.ClientCredential is CertificateClientCredential certificateCredential)
- {
- if (certificateCredential.Certificate == null)
- {
- throw new MsalClientException(
- MsalError.MtlsCertificateNotProvided,
- MsalErrorMessage.MtlsCertificateNotProvidedMessage);
- }
-
- CommonParameters.AuthenticationOperation = new MtlsPopAuthenticationOperation(certificateCredential.Certificate);
- CommonParameters.MtlsCertificate = certificateCredential.Certificate;
- }
-
CommonParameters.IsMtlsPopRequested = true;
return this;
}
+
///
/// Please use WithAzureRegion on the ConfidentialClientApplicationBuilder object
///
@@ -193,21 +185,51 @@ internal override Task ExecuteInternalAsync(CancellationTo
/// for a comment inside this function for AzureRegion.
protected override void Validate()
{
- if (CommonParameters.MtlsCertificate != null)
+ base.Validate();
+
+ // MTLS PoP validation
+ if (CommonParameters.IsMtlsPopRequested)
{
- // Check for Azure region only if the authority is AAD
- // AzureRegion is by default set to null or set to null when the application is created
- // with region set to DisableForceRegion (see ConfidentialClientApplicationBuilder.Validate)
- if (ServiceBundle.Config.Authority.AuthorityInfo.AuthorityType == AuthorityType.Aad &&
- ServiceBundle.Config.AzureRegion == null)
+ var credential = ServiceBundle.Config.ClientCredential;
+
+ // Check 1: Credential type - only specific types support MTLS
+ // CertificateAndClaimsClientCredential (WithClientClaims) does NOT support MTLS
+ bool supportsMtls = credential is CertificateClientCredential ||
+ credential is ClientAssertionDelegateCredential;
+
+ if (!supportsMtls)
{
throw new MsalClientException(
- MsalError.MtlsPopWithoutRegion,
- MsalErrorMessage.MtlsPopWithoutRegion);
+ MsalError.MtlsCertificateNotProvided,
+ "MTLS Proof-of-Possession requires a certificate-based credential. " +
+ "Use WithCertificate() to configure a certificate, or use WithClientAssertion() with a TokenBindingCertificate. " +
+ "WithClientClaims() is not supported for MTLS PoP.");
}
- }
- base.Validate();
+ // Check 2: For CertificateClientCredential, validate cert and region now (fail-fast)
+ // For ClientAssertionDelegateCredential, defer to runtime (can't check TokenBindingCertificate synchronously)
+ if (credential is CertificateClientCredential certCred)
+ {
+ // Validate cert is provided
+ if (certCred.Certificate == null)
+ {
+ throw new MsalClientException(
+ MsalError.MtlsCertificateNotProvided,
+ "MTLS Proof-of-Possession requires a certificate, but WithCertificate() was called with a null certificate.");
+ }
+
+ // For AAD, validate region is configured
+ if (ServiceBundle.Config.Authority.AuthorityInfo.AuthorityType == AuthorityType.Aad &&
+ ServiceBundle.Config.AzureRegion == null)
+ {
+ throw new MsalClientException(
+ MsalError.MtlsPopWithoutRegion,
+ MsalErrorMessage.MtlsPopWithoutRegion);
+ }
+ }
+ // Note: For ClientAssertionDelegateCredential, cert availability and region checks
+ // happen at runtime in orchestrator (need to call GetAssertionAsync to check TokenBindingCertificate)
+ }
// Force refresh + AccessTokenHashToRefresh APIs cannot be used together
if (Parameters.ForceRefresh && !string.IsNullOrEmpty(Parameters.AccessTokenHashToRefresh))
diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ClientApplicationBaseExecutor.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ClientApplicationBaseExecutor.cs
index 0556dd2569..6e98ebbb8c 100644
--- a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ClientApplicationBaseExecutor.cs
+++ b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ClientApplicationBaseExecutor.cs
@@ -28,7 +28,7 @@ public async Task ExecuteAsync(
AcquireTokenSilentParameters silentParameters,
CancellationToken cancellationToken)
{
- var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
var requestParameters = await _clientApplicationBase.CreateRequestParametersAsync(
commonParameters,
@@ -47,7 +47,7 @@ public async Task ExecuteAsync(
AcquireTokenByRefreshTokenParameters refreshTokenParameters,
CancellationToken cancellationToken)
{
- var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
if (commonParameters.Scopes == null || !commonParameters.Scopes.Any())
{
commonParameters.Scopes = new SortedSet
diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs
index 82b6a0be09..2fd1379069 100644
--- a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs
+++ b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs
@@ -2,18 +2,13 @@
// Licensed under the MIT License.
using System;
-using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.ApiConfig.Parameters;
using Microsoft.Identity.Client.AuthScheme.PoP;
-using Microsoft.Identity.Client.Instance.Discovery;
-using Microsoft.Identity.Client.Instance.Validation;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Internal.ClientCredential;
using Microsoft.Identity.Client.Internal.Requests;
-using Microsoft.Identity.Client.ManagedIdentity;
-using Microsoft.Identity.Client.Utils;
namespace Microsoft.Identity.Client.ApiConfig.Executors
{
@@ -37,7 +32,7 @@ public async Task ExecuteAsync(
AcquireTokenByAuthorizationCodeParameters authorizationCodeParameters,
CancellationToken cancellationToken)
{
- RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
AuthenticationRequestParameters requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
commonParameters,
@@ -59,10 +54,27 @@ public async Task ExecuteAsync(
AcquireTokenForClientParameters clientParameters,
CancellationToken cancellationToken)
{
- await commonParameters.InitMtlsPopParametersAsync(ServiceBundle, cancellationToken)
- .ConfigureAwait(false);
+ // Resolve certificate and configure MTLS PoP if requested
+ ClientCertificateContext certContext = null;
+ if (commonParameters.IsMtlsPopRequested)
+ {
+ certContext = await ClientCertificateOrchestrator.ResolveCertificateAsync(
+ ServiceBundle.Config.ClientCredential,
+ ServiceBundle,
+ isMtlsPopRequested: true, // Already validated by if condition
+ commonParameters.Claims,
+ cancellationToken).ConfigureAwait(false);
+
+ // Set authentication operation for MTLS PoP
+ commonParameters.AuthenticationOperation = new MtlsPopAuthenticationOperation(
+ certContext.Certificate);
+ }
+
+ RequestContext requestContext = CreateRequestContextAndLogVersionInfo(
+ commonParameters.CorrelationId,
+ certContext?.Certificate,
+ cancellationToken);
- RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
AuthenticationRequestParameters requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
commonParameters,
@@ -72,6 +84,12 @@ await commonParameters.InitMtlsPopParametersAsync(ServiceBundle, cancellationTok
requestParams.SendX5C = clientParameters.SendX5C ?? false;
+ // Store resolved certificate context for telemetry and tracking
+ if (certContext != null)
+ {
+ requestParams.CertificateContext = certContext;
+ }
+
var handler = new ClientCredentialRequest(
ServiceBundle,
requestParams,
@@ -85,7 +103,7 @@ public async Task ExecuteAsync(
AcquireTokenOnBehalfOfParameters onBehalfOfParameters,
CancellationToken cancellationToken)
{
- RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
AuthenticationRequestParameters requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
commonParameters,
@@ -110,7 +128,7 @@ public async Task ExecuteAsync(
GetAuthorizationRequestUrlParameters authorizationRequestUrlParameters,
CancellationToken cancellationToken)
{
- RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
AuthenticationRequestParameters requestParameters = await _confidentialClientApplication.CreateRequestParametersAsync(
commonParameters,
@@ -147,7 +165,7 @@ public async Task ExecuteAsync(
AcquireTokenByUsernamePasswordParameters usernamePasswordParameters,
CancellationToken cancellationToken)
{
- RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
AuthenticationRequestParameters requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
commonParameters,
diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ManagedIdentityExecutor.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ManagedIdentityExecutor.cs
index c6d70db147..2076a66ab5 100644
--- a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ManagedIdentityExecutor.cs
+++ b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ManagedIdentityExecutor.cs
@@ -1,15 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.ApiConfig.Parameters;
-using Microsoft.Identity.Client.Instance.Discovery;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Internal.Requests;
-using Microsoft.Identity.Client.ManagedIdentity;
-using Microsoft.Identity.Client.Utils;
namespace Microsoft.Identity.Client.ApiConfig.Executors
{
@@ -33,7 +29,7 @@ public async Task ExecuteAsync(
AcquireTokenForManagedIdentityParameters managedIdentityParameters,
CancellationToken cancellationToken)
{
- var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
var requestParams = await _managedIdentityApplication.CreateRequestParametersAsync(
commonParameters,
diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/PublicClientExecutor.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/PublicClientExecutor.cs
index 28e25abf65..4280b65753 100644
--- a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/PublicClientExecutor.cs
+++ b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/PublicClientExecutor.cs
@@ -1,13 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.ApiConfig.Parameters;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Internal.Requests;
-using Microsoft.Identity.Client.UI;
namespace Microsoft.Identity.Client.ApiConfig.Executors
{
@@ -26,7 +24,7 @@ public async Task ExecuteAsync(
AcquireTokenInteractiveParameters interactiveParameters,
CancellationToken cancellationToken)
{
- var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
AuthenticationRequestParameters requestParams = await _publicClientApplication.CreateRequestParametersAsync(
commonParameters,
@@ -48,7 +46,7 @@ public async Task ExecuteAsync(
AcquireTokenWithDeviceCodeParameters deviceCodeParameters,
CancellationToken cancellationToken)
{
- var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
var requestParams = await _publicClientApplication.CreateRequestParametersAsync(
commonParameters,
@@ -69,7 +67,7 @@ public async Task ExecuteAsync(
AcquireTokenByIntegratedWindowsAuthParameters integratedWindowsAuthParameters,
CancellationToken cancellationToken)
{
- var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
var requestParams = await _publicClientApplication.CreateRequestParametersAsync(
commonParameters,
@@ -90,7 +88,7 @@ public async Task ExecuteAsync(
AcquireTokenByUsernamePasswordParameters usernamePasswordParameters,
CancellationToken cancellationToken)
{
- var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);
+ var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, null, cancellationToken);
var requestParams = await _publicClientApplication.CreateRequestParametersAsync(
commonParameters,
diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenCommonParameters.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenCommonParameters.cs
index 28e732efa6..cc477fc5ef 100644
--- a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenCommonParameters.cs
+++ b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenCommonParameters.cs
@@ -35,7 +35,6 @@ internal class AcquireTokenCommonParameters
public IDictionary ExtraHttpHeaders { get; set; }
public PoPAuthenticationConfiguration PopAuthenticationConfiguration { get; set; }
public IList> OnBeforeTokenRequestHandler { get; internal set; }
- public X509Certificate2 MtlsCertificate { get; internal set; }
public List AdditionalCacheParameters { get; set; }
public SortedList>> CacheKeyComponents { get; internal set; }
public string FmiPathSuffix { get; internal set; }
@@ -49,74 +48,5 @@ internal class AcquireTokenCommonParameters
/// Returns null for non-attested flows.
///
public Func> AttestationTokenProvider { get; set; }
-
- internal async Task InitMtlsPopParametersAsync(IServiceBundle serviceBundle, CancellationToken ct)
- {
- if (!IsMtlsPopRequested)
- {
- return; // PoP not requested
- }
-
- // ────────────────────────────────────
- // Case 1 – Certificate credential
- // ────────────────────────────────────
- if (serviceBundle.Config.ClientCredential is CertificateClientCredential certCred)
- {
- if (certCred.Certificate == null)
- {
- throw new MsalClientException(
- MsalError.MtlsCertificateNotProvided,
- MsalErrorMessage.MtlsCertificateNotProvidedMessage);
- }
-
- return;
- }
-
- // ────────────────────────────────────
- // Case 2 – Client‑assertion delegate
- // ────────────────────────────────────
- if (serviceBundle.Config.ClientCredential is ClientAssertionDelegateCredential cadc)
- {
- var opts = new AssertionRequestOptions
- {
- ClientID = serviceBundle.Config.ClientId,
- ClientCapabilities = serviceBundle.Config.ClientCapabilities,
- Claims = Claims,
- CancellationToken = ct
- };
-
- ClientSignedAssertion ar = await cadc.GetAssertionAsync(opts, ct).ConfigureAwait(false);
-
- if (ar.TokenBindingCertificate == null)
- {
- throw new MsalClientException(
- MsalError.MtlsCertificateNotProvided,
- MsalErrorMessage.MtlsCertificateNotProvidedMessage);
- }
-
- InitMtlsPopParameters(ar.TokenBindingCertificate, serviceBundle);
- return;
- }
-
- // ────────────────────────────────────
- // Case 3 – Any other credential (client‑secret etc.)
- // ────────────────────────────────────
- throw new MsalClientException(
- MsalError.MtlsCertificateNotProvided,
- MsalErrorMessage.MtlsCertificateNotProvidedMessage);
- }
-
- private void InitMtlsPopParameters(X509Certificate2 cert, IServiceBundle serviceBundle)
- {
- // region check (AAD only)
- if (serviceBundle.Config.Authority.AuthorityInfo.AuthorityType == AuthorityType.Aad &&
- serviceBundle.Config.AzureRegion == null)
- {
- throw new MsalClientException(MsalError.MtlsPopWithoutRegion, MsalErrorMessage.MtlsPopWithoutRegion);
- }
-
- AuthenticationOperation = new MtlsPopAuthenticationOperation(cert);
- MtlsCertificate = cert;
- }
}
}
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/AssertionRequestOptions.cs b/src/client/Microsoft.Identity.Client/AppConfig/AssertionRequestOptions.cs
index 4cacac7cb2..4fcf703e49 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/AssertionRequestOptions.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/AssertionRequestOptions.cs
@@ -76,5 +76,11 @@ internal AssertionRequestOptions(ApplicationConfiguration appConfig, string toke
/// FMI path to be used for client assertion. Tokens are associated with this path in the cache.
///
public string ClientAssertionFmiPath { get; set; }
+
+ ///
+ /// Indicates whether MTLS Proof-of-Possession is requested for this authentication operation.
+ /// When true, the certificate (if provided) will be used for TLS binding rather than just JWT signing.
+ ///
+ public bool IsMtlsPopRequested { get; set; }
}
}
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
index cc7480c734..17b6c8bd8f 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
@@ -14,7 +14,7 @@
namespace Microsoft.Identity.Client.Internal.ClientCredential
{
- internal class CertificateAndClaimsClientCredential : IClientCredential
+ internal class CertificateAndClaimsClientCredential : IClientCredential, IClientCertificateProvider
{
private readonly IDictionary _claimsToSign;
private readonly bool _appendDefaultClaims = true;
@@ -60,16 +60,42 @@ public async Task AddConfidentialClientParametersAsync(
// Log the incoming request parameters for diagnostic purposes
requestParameters.RequestContext.Logger.Verbose(() => $"Building assertion from certificate with clientId: {clientId} at endpoint: {tokenEndpoint}");
- if (requestParameters.MtlsCertificate == null)
+ // Resolve certificate if not already resolved by orchestrator
+ // Orchestrator sets CertificateContext when MTLS is requested, but this method is also called
+ // in non-MTLS scenarios where cert needs to be resolved for JWT signing
+ if (requestParameters.CertificateContext == null)
{
- requestParameters.RequestContext.Logger.Verbose(() => "Proceeding with JWT token creation and adding client assertion.");
+ requestParameters.RequestContext.Logger.Verbose(() => "Certificate not yet resolved. Resolving from provider for JWT signing.");
+
+ // Build options for certificate resolution
+ var options = new AssertionRequestOptions(
+ requestParameters.AppConfig,
+ tokenEndpoint,
+ requestParameters.AuthorityManager.Authority.TenantId)
+ {
+ Claims = requestParameters.Claims,
+ ClientCapabilities = requestParameters.AppConfig.ClientCapabilities,
+ IsMtlsPopRequested = false, // Non-MTLS path - cert used for JWT signing
+ CancellationToken = cancellationToken
+ };
+
+ // Resolve certificate context via interface method
+ ClientCertificateContext certContext = await GetCertificateAsync(options, cancellationToken).ConfigureAwait(false);
+
+ // Validate the resolved certificate (GetCertificateAsync returns null if provider returns null)
+ ValidateCertificate(certContext?.Certificate, requestParameters.RequestContext.Logger);
+
+ requestParameters.CertificateContext = certContext;
+ }
- // Resolve the certificate via the provider
- X509Certificate2 certificate = await ResolveCertificateAsync(requestParameters, tokenEndpoint, cancellationToken).ConfigureAwait(false);
+ // Check if MTLS PoP is being used (cert already in context with MTLS usage)
+ bool isMtlsPoP = requestParameters.CertificateContext.Usage == ClientCertificateUsage.MtlsBinding;
- // Store the resolved certificate in request parameters for later use (e.g., ExecutionResult)
- requestParameters.ResolvedCertificate = certificate;
+ if (!isMtlsPoP)
+ {
+ requestParameters.RequestContext.Logger.Verbose(() => "Proceeding with JWT token creation and adding client assertion.");
+ X509Certificate2 certificate = requestParameters.CertificateContext.Certificate;
bool useSha2 = requestParameters.AuthorityManager.Authority.AuthorityInfo.IsSha2CredentialSupported;
JsonWebToken jwtToken;
@@ -101,48 +127,21 @@ public async Task AddConfidentialClientParametersAsync(
{
// Log that MTLS PoP is required and JWT token creation is skipped
requestParameters.RequestContext.Logger.Verbose(() => "MTLS PoP Client credential request. Skipping client assertion.");
-
- // Store the mTLS certificate in request parameters for later use (e.g., ExecutionResult)
- requestParameters.ResolvedCertificate = requestParameters.MtlsCertificate;
}
}
///
- /// Resolves the certificate to use for signing the client assertion.
- /// Invokes the certificate provider delegate to get the certificate.
+ /// Validates that a certificate is suitable for use in authentication.
///
- /// The authentication request parameters containing app config
- /// The token endpoint URL
- /// Cancellation token for the async operation
- /// The X509Certificate2 to use for signing
- /// Thrown if the certificate provider returns null or an invalid certificate
- private async Task ResolveCertificateAsync(
- AuthenticationRequestParameters requestParameters,
- string tokenEndpoint,
- CancellationToken cancellationToken)
+ /// The certificate to validate
+ /// Logger for diagnostic messages
+ /// Thrown if certificate is null or invalid
+ private static void ValidateCertificate(X509Certificate2 certificate, ILoggerAdapter logger)
{
- requestParameters.RequestContext.Logger.Verbose(
- () => "[CertificateAndClaimsClientCredential] Resolving certificate from provider.");
-
- // Create AssertionRequestOptions for the callback
- var options = new AssertionRequestOptions(
- requestParameters.AppConfig,
- tokenEndpoint,
- requestParameters.AuthorityManager.Authority.TenantId)
- {
- Claims = requestParameters.Claims,
- ClientCapabilities = requestParameters.AppConfig.ClientCapabilities,
- CancellationToken = cancellationToken
- };
-
- // Invoke the provider to get the certificate
- X509Certificate2 certificate = await _certificateProvider(options).ConfigureAwait(false);
-
// Validate the certificate returned by the provider
if (certificate == null)
{
- requestParameters.RequestContext.Logger.Error(
- "[CertificateAndClaimsClientCredential] Certificate provider returned null.");
+ logger.Error("[CertificateAndClaimsClientCredential] Certificate provider returned null.");
throw new MsalClientException(
MsalError.InvalidClientAssertion,
@@ -153,8 +152,7 @@ private async Task ResolveCertificateAsync(
{
if (!certificate.HasPrivateKey)
{
- requestParameters.RequestContext.Logger.Error(
- "[CertificateAndClaimsClientCredential] Certificate from provider does not have a private key.");
+ logger.Error("[CertificateAndClaimsClientCredential] Certificate does not have a private key.");
throw new MsalClientException(
MsalError.CertWithoutPrivateKey,
@@ -163,8 +161,7 @@ private async Task ResolveCertificateAsync(
}
catch (System.Security.Cryptography.CryptographicException ex)
{
- requestParameters.RequestContext.Logger.Error(
- "[CertificateAndClaimsClientCredential] A cryptographic error occurred while accessing the certificate.");
+ logger.Error("[CertificateAndClaimsClientCredential] A cryptographic error occurred while accessing the certificate.");
throw new MsalClientException(
MsalError.CryptographicError,
@@ -172,11 +169,32 @@ private async Task ResolveCertificateAsync(
ex);
}
- requestParameters.RequestContext.Logger.Info(
- () => $"[CertificateAndClaimsClientCredential] Successfully resolved certificate from provider. " +
- $"Thumbprint: {certificate.Thumbprint}");
+ logger.Info(() => $"[CertificateAndClaimsClientCredential] Successfully validated certificate. Thumbprint: {certificate.Thumbprint}");
+ }
+
+ public async Task GetCertificateAsync(
+ AssertionRequestOptions options,
+ CancellationToken cancellationToken)
+ {
+ // Resolve the certificate via the provider delegate
+ X509Certificate2 certificate = await _certificateProvider(options).ConfigureAwait(false);
+
+ if (certificate == null)
+ {
+ return null;
+ }
+
+ // Determine usage based on whether MTLS PoP is requested
+ // Same certificate can be used for either JWT signing OR MTLS binding
+ var usage = options.IsMtlsPopRequested
+ ? ClientCertificateUsage.MtlsBinding
+ : ClientCertificateUsage.Assertion;
- return certificate;
+ return new ClientCertificateContext
+ {
+ Certificate = certificate,
+ Usage = usage
+ };
}
}
}
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientAssertionDelegateCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientAssertionDelegateCredential.cs
index 19190df8f9..e57de57326 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientAssertionDelegateCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientAssertionDelegateCredential.cs
@@ -19,7 +19,7 @@ namespace Microsoft.Identity.Client.Internal.ClientCredential
/// Handles client assertions supplied via a delegate that returns an
/// (JWT + optional certificate bound for mTLS‑PoP).
///
- internal sealed class ClientAssertionDelegateCredential : IClientCredential
+ internal sealed class ClientAssertionDelegateCredential : IClientCredential, IClientCertificateProvider
{
private readonly Func> _provider;
@@ -82,5 +82,28 @@ public async Task AddConfidentialClientParametersAsync(
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertion, resp.Assertion);
}
+
+ public async Task GetCertificateAsync(
+ AssertionRequestOptions options,
+ CancellationToken cancellationToken)
+ {
+ // Call the assertion provider to get the signed assertion
+ ClientSignedAssertion assertion = await _provider(options, cancellationToken)
+ .ConfigureAwait(false);
+
+ // If no TokenBindingCertificate, return null (no certificate provided)
+ if (assertion?.TokenBindingCertificate == null)
+ {
+ return null;
+ }
+
+ // TokenBindingCertificate is ALWAYS for MTLS binding (by definition)
+ // It's not used for JWT signing - it's for TLS channel binding
+ return new ClientCertificateContext
+ {
+ Certificate = assertion.TokenBindingCertificate,
+ Usage = ClientCertificateUsage.MtlsBinding
+ };
+ }
}
}
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateContext.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateContext.cs
new file mode 100644
index 0000000000..c67bdfba07
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateContext.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Security.Cryptography.X509Certificates;
+
+namespace Microsoft.Identity.Client.Internal.ClientCredential
+{
+ ///
+ /// Represents a resolved client certificate with metadata about its intended usage.
+ ///
+ ///
+ /// This class encapsulates both the certificate and how it will be used in the authentication flow.
+ /// The same certificate may be used for different purposes (JWT signing vs MTLS binding) depending
+ /// on the request configuration (IsMtlsPopRequested flag).
+ ///
+ internal class ClientCertificateContext
+ {
+ ///
+ /// Gets or sets the X509 certificate to be used for authentication.
+ ///
+ public X509Certificate2 Certificate { get; set; }
+
+ ///
+ /// Gets or sets how this certificate will be used in the authentication flow.
+ ///
+ public ClientCertificateUsage Usage { get; set; }
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateOrchestrator.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateOrchestrator.cs
new file mode 100644
index 0000000000..8f9358a07f
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateOrchestrator.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Identity.Client.Internal;
+
+namespace Microsoft.Identity.Client.Internal.ClientCredential
+{
+ ///
+ /// Orchestrates client certificate resolution and validation for confidential client authentication.
+ ///
+ ///
+ /// This orchestrator encapsulates the complexity of resolving certificates from various credential types
+ /// and applies appropriate validation based on the authentication scenario (JWT signing vs MTLS PoP).
+ /// It handles both certificate-based credentials (WithCertificate) and assertion-based credentials
+ /// (WithClientAssertion) that may optionally provide a TokenBindingCertificate.
+ ///
+ internal static class ClientCertificateOrchestrator
+ {
+ ///
+ /// Resolves and validates a client certificate for authentication.
+ ///
+ /// The client credential which may or may not support certificate provision.
+ /// Service bundle containing configuration and authority information.
+ /// Flag indicating if MTLS Proof-of-Possession is requested.
+ /// Optional claims to include in the certificate request.
+ /// Cancellation token for the async operation.
+ ///
+ /// A containing the resolved certificate and its intended usage.
+ /// The certificate may be null if the credential doesn't support certificates and MTLS is not requested.
+ ///
+ ///
+ /// Thrown if:
+ /// - MTLS PoP is requested but the credential type does not support certificates
+ /// - The certificate provider returns null when MTLS is requested
+ /// - MTLS PoP is requested for AAD without regional endpoint configuration
+ ///
+ public static async Task ResolveCertificateAsync(
+ IClientCredential credential,
+ IServiceBundle serviceBundle,
+ bool isMtlsPopRequested,
+ string claims,
+ CancellationToken cancellationToken)
+ {
+ ClientCertificateContext certContext = null;
+
+ // Check if credential supports certificate provision
+ if (credential is IClientCertificateProvider certProvider)
+ {
+ // Build options for certificate resolution
+ var options = new AssertionRequestOptions
+ {
+ ClientID = serviceBundle.Config.ClientId,
+ Claims = claims,
+ ClientCapabilities = serviceBundle.Config.ClientCapabilities,
+ IsMtlsPopRequested = isMtlsPopRequested, // Pass flag to provider
+ CancellationToken = cancellationToken
+ };
+
+ // Resolve the certificate context from the provider
+ // Provider now returns context with usage already determined
+ certContext = await certProvider
+ .GetCertificateAsync(options, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ // Validate if MTLS PoP is requested
+ if (isMtlsPopRequested)
+ {
+ // Check 1: Certificate must be provided (before checking region)
+ if (certContext?.Certificate == null)
+ {
+ // Credential doesn't support certs or provider returned null
+ string credentialTypeName = credential.GetType().Name;
+ throw new MsalClientException(
+ MsalError.MtlsCertificateNotProvided,
+ $"MTLS Proof-of-Possession requires a certificate. " +
+ $"The credential type '{credentialTypeName}' does not support certificates or did not provide one. " +
+ $"Use WithCertificate() to configure a certificate, or use WithClientAssertion() with a TokenBindingCertificate.");
+ }
+
+ // Check 2: Validate certificate for MTLS (region check happens here)
+ ClientCertificateValidation.ValidateForMtlsPoP(certContext.Certificate, serviceBundle);
+ }
+
+ // Return the context as-is (usage already set by provider)
+ return certContext;
+ }
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateUsage.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateUsage.cs
new file mode 100644
index 0000000000..4088d501bb
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateUsage.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+namespace Microsoft.Identity.Client.Internal.ClientCredential
+{
+ ///
+ /// Describes how a client certificate is used in the authentication flow.
+ ///
+ internal enum ClientCertificateUsage
+ {
+ ///
+ /// No certificate or not applicable.
+ ///
+ None,
+
+ ///
+ /// Certificate is used to sign client assertions (JWT).
+ /// This is the default usage for certificate-based authentication.
+ ///
+ Assertion,
+
+ ///
+ /// Certificate is used for MTLS Proof-of-Possession binding.
+ /// The certificate is used for the TLS handshake and binds the token to the TLS channel.
+ ///
+ MtlsBinding
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateValidation.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateValidation.cs
new file mode 100644
index 0000000000..f3af2a1b90
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCertificateValidation.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Identity.Client.Internal;
+
+namespace Microsoft.Identity.Client.Internal.ClientCredential
+{
+ ///
+ /// Shared validation utilities for client certificate scenarios.
+ ///
+ internal static class ClientCertificateValidation
+ {
+ ///
+ /// Validates that a certificate is suitable for MTLS Proof-of-Possession.
+ ///
+ /// The certificate to validate.
+ /// Service bundle containing configuration.
+ ///
+ /// Thrown if the certificate is null or if MTLS PoP is requested for AAD without regional configuration.
+ ///
+ public static void ValidateForMtlsPoP(
+ X509Certificate2 certificate,
+ IServiceBundle serviceBundle)
+ {
+ // Validate certificate presence
+ if (certificate == null)
+ {
+ throw new MsalClientException(
+ MsalError.MtlsCertificateNotProvided,
+ MsalErrorMessage.MtlsCertificateNotProvidedMessage);
+ }
+
+ // Region validation: MTLS PoP for AAD requires regional endpoint configuration
+ if (serviceBundle.Config.Authority.AuthorityInfo.AuthorityType == AuthorityType.Aad &&
+ serviceBundle.Config.AzureRegion == null)
+ {
+ throw new MsalClientException(
+ MsalError.MtlsPopWithoutRegion,
+ MsalErrorMessage.MtlsPopWithoutRegion);
+ }
+ }
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCertificateProvider.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCertificateProvider.cs
new file mode 100644
index 0000000000..c32428575f
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCertificateProvider.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Identity.Client.Internal.ClientCredential
+{
+ ///
+ /// Adapter interface for providing client certificates from various sources.
+ ///
+ ///
+ /// This interface enables the adapter pattern to unify certificate resolution
+ /// from different sources (IClientCredential implementations, managed identity sources, etc.).
+ /// Implementations return a that includes both the certificate
+ /// and its intended usage (Assertion for JWT signing vs MtlsBinding for TLS binding).
+ ///
+ internal interface IClientCertificateProvider
+ {
+ ///
+ /// Gets the client certificate context asynchronously, including the certificate and its usage type.
+ ///
+ ///
+ /// Options for the certificate request, including client ID, token endpoint, and whether MTLS PoP is requested.
+ /// The IsMtlsPopRequested flag helps determine the certificate usage type.
+ ///
+ /// Cancellation token for the async operation.
+ ///
+ /// A containing the certificate and its usage type,
+ /// or null if this provider does not supply certificates.
+ /// The usage type is determined by:
+ /// - TokenBindingCertificate → always
+ /// - Regular certificate → if MTLS PoP requested,
+ /// otherwise
+ ///
+ Task GetCertificateAsync(
+ AssertionRequestOptions options,
+ CancellationToken cancellationToken);
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs
index bb23445538..920cbe0035 100644
--- a/src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs
@@ -14,6 +14,7 @@
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Client.Instance;
+using Microsoft.Identity.Client.Internal.ClientCredential;
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;
using Microsoft.Identity.Client.Utils;
@@ -111,15 +112,13 @@ public AuthenticationRequestParameters(
public Guid CorrelationId => _commonParameters.CorrelationId;
- public X509Certificate2 MtlsCertificate => _commonParameters.MtlsCertificate;
-
public bool IsMtlsPopRequested => _commonParameters.IsMtlsPopRequested;
///
- /// The certificate resolved and used for client authentication (if certificate-based authentication was used).
- /// This is set during the token request when the certificate is resolved.
+ /// The certificate context containing the resolved certificate and its usage type.
+ /// This is set during the token request when the certificate is resolved via the orchestrator.
///
- public X509Certificate2 ResolvedCertificate { get; set; }
+ public ClientCertificateContext CertificateContext { get; set; }
///
/// Indicates if the user configured claims via .WithClaims. Not affected by Client Capabilities
diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs
index 194e5cdbb0..2b481bd76c 100644
--- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs
@@ -12,6 +12,7 @@
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Client.Instance;
+using Microsoft.Identity.Client.Internal.ClientCredential;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.Utils;
@@ -179,6 +180,11 @@ private async Task GetAccessTokenAsync(
{
retryCount++;
logger.Info($"[ClientCredentialRequest] OnMsalServiceFailure returned true. Retrying token request (Retry #{retryCount}).");
+
+ // Clear certificate context to force re-resolution on retry
+ // This enables certificate rotation scenarios where provider returns different cert on retry
+ AuthenticationRequestParameters.CertificateContext = null;
+
continue; // Retry the loop
}
@@ -271,7 +277,7 @@ private async Task InvokeOnMsalServiceFailureCallbackAsync(
Successful = false,
Result = null,
Exception = serviceException,
- ClientCertificate = AuthenticationRequestParameters.ResolvedCertificate
+ ClientCertificate = AuthenticationRequestParameters.CertificateContext?.Certificate
};
bool shouldRetry = await AuthenticationRequestParameters.AppConfig
@@ -319,7 +325,7 @@ private async Task InvokeOnCompletionCallbackAsync(
Successful = authResult != null,
Result = authResult,
Exception = exception,
- ClientCertificate = AuthenticationRequestParameters.ResolvedCertificate
+ ClientCertificate = AuthenticationRequestParameters.CertificateContext?.Certificate
};
await AuthenticationRequestParameters.AppConfig
@@ -396,12 +402,11 @@ private bool ShouldUseCachedToken(MsalAccessTokenCacheItem cacheItem)
return false;
}
- // 2) If an mTLS cert is supplied for THIS request, reuse cache only if
+ // 2) If an mTLS cert is used for token binding (not just JWT signing), reuse cache only if
// the cached token's KeyId matches the one provided in the request.
- X509Certificate2 requestCert = AuthenticationRequestParameters.MtlsCertificate;
-
- if (requestCert != null)
+ if (AuthenticationRequestParameters.CertificateContext?.Usage == ClientCertificateUsage.MtlsBinding)
{
+ X509Certificate2 requestCert = AuthenticationRequestParameters.CertificateContext.Certificate;
string expectedKid = CoreHelpers.ComputeX5tS256KeyId(requestCert);
// If the certificate cannot produce a valid KeyId (SPKI-SHA256), expectedKid will be null or empty.
@@ -409,12 +414,12 @@ private bool ShouldUseCachedToken(MsalAccessTokenCacheItem cacheItem)
if (!string.Equals(cacheItem.KeyId, expectedKid, StringComparison.Ordinal))
{
AuthenticationRequestParameters.RequestContext.Logger.Verbose(() =>
- "[ClientCredentialRequest] Cached token KeyId does not match request certificate (SPKI-SHA256 mismatch). Bypassing cache.");
+ "[ClientCredentialRequest] Cached token KeyId does not match MTLS binding certificate (SPKI-SHA256 mismatch). Bypassing cache.");
return false;
}
AuthenticationRequestParameters.RequestContext.Logger.Verbose(() =>
- "[ClientCredentialRequest] Cached token KeyId matches request certificate (SPKI-SHA256). Using cached token.");
+ "[ClientCredentialRequest] Cached token KeyId matches MTLS binding certificate (SPKI-SHA256). Using cached token.");
}
// 3) If the token's hash matches AccessTokenHashToRefresh, ignore it
diff --git a/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs b/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
index 2753726580..1a602b132f 100644
--- a/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
+++ b/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
@@ -38,7 +38,7 @@ public TokenClient(AuthenticationRequestParameters requestParams)
_oAuth2Client = new OAuth2Client(
_serviceBundle.ApplicationLogger,
_serviceBundle.HttpManager,
- requestParams.MtlsCertificate);
+ requestParams.CertificateContext?.Certificate);
}
public async Task SendTokenRequestAsync(
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 0db0290228..b7c56e3c35 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
@@ -9,4 +9,6 @@ Microsoft.Identity.Client.ManagedIdentityPopExtensions
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
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.AssertionRequestOptions.IsMtlsPopRequested.get -> bool
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.set -> void
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
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 4741e5090d..616cdda0fa 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
@@ -9,4 +9,6 @@ Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(S
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
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.get -> bool
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.set -> void
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> 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 37f859d5dd..be8c6e5c3d 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
@@ -9,4 +9,6 @@ static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquire
Microsoft.Identity.Client.ManagedIdentityPopExtensions
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
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.get -> bool
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.set -> void
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
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 37f859d5dd..be8c6e5c3d 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
@@ -9,4 +9,6 @@ static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquire
Microsoft.Identity.Client.ManagedIdentityPopExtensions
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
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.get -> bool
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.set -> void
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
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 37f859d5dd..5eeaf3c08b 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
@@ -3,6 +3,8 @@ Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertif
Microsoft.Identity.Client.AppConfig.CertificateOptions.AssociateTokensWithCertificate.init -> void
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.get -> bool
Microsoft.Identity.Client.AppConfig.CertificateOptions.SendX5C.init -> void
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.get -> bool
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.set -> 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
static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquireTokenParameterBuilderExtension.WithExtraClientAssertionClaims(this Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder builder, string clientAssertionClaims) -> Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder
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 37f859d5dd..be8c6e5c3d 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
@@ -9,4 +9,6 @@ static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquire
Microsoft.Identity.Client.ManagedIdentityPopExtensions
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
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.get -> bool
+Microsoft.Identity.Client.AssertionRequestOptions.IsMtlsPopRequested.set -> void
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpAndServiceBundle.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpAndServiceBundle.cs
index e3cb1fac57..972b7eadf4 100644
--- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpAndServiceBundle.cs
+++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpAndServiceBundle.cs
@@ -67,7 +67,7 @@ public AuthenticationRequestParameters CreateAuthenticationRequestParameters(
};
var authorityObj = Authority.CreateAuthority(authority, validateAuthority);
- var requestContext = new RequestContext(ServiceBundle, Guid.NewGuid(), commonParameters.MtlsCertificate);
+ var requestContext = new RequestContext(ServiceBundle, Guid.NewGuid(), null);
AuthenticationRequestParameters authenticationRequestParameters =
new AuthenticationRequestParameters(
ServiceBundle,
diff --git a/tests/Microsoft.Identity.Test.Common/TestCommon.cs b/tests/Microsoft.Identity.Test.Common/TestCommon.cs
index 0fd0163719..f99e204a0f 100644
--- a/tests/Microsoft.Identity.Test.Common/TestCommon.cs
+++ b/tests/Microsoft.Identity.Test.Common/TestCommon.cs
@@ -98,7 +98,7 @@ public static AuthenticationRequestParameters CreateAuthenticationRequestParamet
};
authority ??= Authority.CreateAuthority(TestConstants.AuthorityTestTenant);
- requestContext ??= new RequestContext(serviceBundle, Guid.NewGuid(), commonParameters.MtlsCertificate)
+ requestContext ??= new RequestContext(serviceBundle, Guid.NewGuid(), null)
{
ApiEvent = new Client.TelemetryCore.Internal.Events.ApiEvent(Guid.NewGuid())
};
diff --git a/tests/Microsoft.Identity.Test.Unit/RequestsTests/SilentRequestTests.cs b/tests/Microsoft.Identity.Test.Unit/RequestsTests/SilentRequestTests.cs
index 7218fbff17..8df6d74100 100644
--- a/tests/Microsoft.Identity.Test.Unit/RequestsTests/SilentRequestTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/RequestsTests/SilentRequestTests.cs
@@ -277,7 +277,7 @@ public AuthenticationRequestParameters CreateRequestParams(
AuthorityOverride = authorityOverride
};
- var requestContext = new RequestContext(ServiceBundle, Guid.NewGuid(), commonParameters.MtlsCertificate);
+ var requestContext = new RequestContext(ServiceBundle, Guid.NewGuid(), null);
var authority = Microsoft.Identity.Client.Instance.Authority.CreateAuthorityForRequestAsync(
requestContext,