Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,25 +94,17 @@ public AcquireTokenForClientParameterBuilder WithSendX5C(bool withSendX5C)
/// For more information, refer to the <see href="https://aka.ms/mtls-pop">Proof-of-Possession documentation</see>.
/// </summary>
/// <returns>The current instance of <see cref="AcquireTokenForClientParameterBuilder"/> to enable method chaining.</returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}


/// <summary>
/// Please use WithAzureRegion on the ConfidentialClientApplicationBuilder object
/// </summary>
Expand Down Expand Up @@ -193,21 +185,51 @@ internal override Task<AuthenticationResult> ExecuteInternalAsync(CancellationTo
/// <seealso cref="ConfidentialClientApplicationBuilder.Validate"/> 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public async Task<AuthenticationResult> 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,
Expand All @@ -47,7 +47,7 @@ public async Task<AuthenticationResult> 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<string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -37,7 +32,7 @@ public async Task<AuthenticationResult> 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,
Expand All @@ -59,10 +54,27 @@ public async Task<AuthenticationResult> 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,
Comment on lines +61 to +62
Copy link
Copy Markdown
Contributor Author

@neha-bhargava neha-bhargava Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the orchestrator is called CC: @gladjohn

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,
Expand All @@ -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,
Expand All @@ -85,7 +103,7 @@ public async Task<AuthenticationResult> 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,
Expand All @@ -110,7 +128,7 @@ public async Task<Uri> 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,
Expand Down Expand Up @@ -147,7 +165,7 @@ public async Task<AuthenticationResult> 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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -33,7 +29,7 @@ public async Task<AuthenticationResult> 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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -26,7 +24,7 @@ public async Task<AuthenticationResult> 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,
Expand All @@ -48,7 +46,7 @@ public async Task<AuthenticationResult> 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,
Expand All @@ -69,7 +67,7 @@ public async Task<AuthenticationResult> 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,
Expand All @@ -90,7 +88,7 @@ public async Task<AuthenticationResult> 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,
Expand Down
Loading