Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ AppPackages
Microsoft.IdentityModel.Clients.ActiveDirectory.XML
*.htm
.nugetPackageRoot/
**/.mono/

# Created by https://www.gitignore.io/api/visualstudio

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Http;
Expand All @@ -16,13 +17,45 @@ internal class RegionAndMtlsDiscoveryProvider : IRegionDiscoveryProvider
public const string PublicEnvForRegional = "login.microsoft.com";
public const string PublicEnvForRegionalMtlsAuth = "mtlsauth.microsoft.com";

// Map of unsupported sovereign cloud hosts for mTLS PoP to their error messages
private static readonly Dictionary<string, string> s_unsupportedMtlsHosts =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "login.usgovcloudapi.net", MsalErrorMessage.MtlsPopNotSupportedForUsGovCloudApiMessage },
{ "login.chinacloudapi.cn", MsalErrorMessage.MtlsPopNotSupportedForChinaCloudApiMessage }
};

public RegionAndMtlsDiscoveryProvider(IHttpManager httpManager)
{
_regionManager = new RegionManager(httpManager);
}

public async Task<InstanceDiscoveryMetadataEntry> GetMetadataAsync(Uri authority, RequestContext requestContext)
{
// Fail fast: Check for unsupported mTLS hosts before any region discovery
if (requestContext.MtlsCertificate != null)
{
string host = authority.Host;

// Check if host is in the unsupported list
if (s_unsupportedMtlsHosts.TryGetValue(host, out string errorMessage))
{
requestContext.Logger.Error($"[Region discovery] mTLS PoP is not supported for host: {host}");
throw new MsalClientException(
MsalError.MtlsPopNotSupportedForEnvironment,
errorMessage);
}

// Check if host starts with "login."
if (!host.StartsWith("login.", StringComparison.OrdinalIgnoreCase))
{
requestContext.Logger.Error($"[Region discovery] mTLS PoP requires hosts to start with 'login.': {host}");
throw new MsalClientException(
MsalError.MtlsPopNotSupportedForEnvironment,
MsalErrorMessage.MtlsPopNotSupportedForNonLoginHostMessage);
}
}

string region = null;
bool isMtlsEnabled = requestContext.MtlsCertificate != null;

Expand Down
6 changes: 6 additions & 0 deletions src/client/Microsoft.Identity.Client/MsalError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,12 @@ public static class MsalError
/// </summary>
public const string MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity";

/// <summary>
/// <para>What happened?</para> mTLS Proof of Possession (mTLS PoP) is not supported for the specified sovereign cloud environment.
/// <para>Mitigation:</para> Use the supported alternative endpoint for the sovereign cloud environment.
/// </summary>
public const string MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment";

/// <summary>
/// <para>What happened?</para> The operation attempted to force a token refresh while also using a token hash.
/// These two options are incompatible because forcing a refresh bypasses token caching,
Expand Down
3 changes: 3 additions & 0 deletions src/client/Microsoft.Identity.Client/MsalErrorMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,9 @@ public static string InvalidTokenProviderResponseValue(string invalidValueName)
public const string MtlsNotSupportedForManagedIdentityMessage = "IMDSv2 flow is not supported on .NET Framework 4.6.2. Cryptographic operations required for managed identity authentication are unavailable on this platform.";
public const string MtlsNotSupportedForNonWindowsMessage = "mTLS PoP with Managed Identity is not supported on this OS. See https://aka.ms/msal-net-pop.";
public const string RegionRequiredForMtlsPopMessage = "Regional auto-detect failed. mTLS Proof-of-Possession requires a region to be specified, as there is no global endpoint for mTLS. See https://aka.ms/msal-net-pop for details.";
public const string MtlsPopNotSupportedForUsGovCloudApiMessage = "login.usgovcloudapi.net is not supported for mTLS PoP, please use login.microsoftonline.us";
public const string MtlsPopNotSupportedForChinaCloudApiMessage = "login.chinacloudapi.cn is not supported for mTLS PoP, please use login.partner.microsoftonline.cn";
public const string MtlsPopNotSupportedForNonLoginHostMessage = "mTLS PoP is only supported for hosts that start with 'login.'. The provided authority host does not meet this requirement. See https://aka.ms/msal-net-pop for details.";
public const string ForceRefreshAndTokenHasNotCompatible = "Cannot specify ForceRefresh and AccessTokenSha256ToRefresh in the same request.";
public const string RequestTimeOut = "Request to the endpoint timed out.";
public const string MalformedOidcAuthorityFormat = "Possible cause: When using Entra External ID, you didn't append /v2.0, for example {0}/v2.0\"";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ 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<Microsoft.Identity.Client.AssertionRequestOptions, System.Threading.Tasks.Task<System.Security.Cryptography.X509Certificates.X509Certificate2>> certificateProvider, Microsoft.Identity.Client.AppConfig.CertificateOptions certificateOptions) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ Microsoft.Identity.Client.ConfidentialClientApplicationBuilder.WithCertificate(S
static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func<Microsoft.Identity.Client.AssertionRequestOptions, System.Threading.Tasks.Task<System.Security.Cryptography.X509Certificates.X509Certificate2>> 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
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ 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
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ 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
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ 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
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ 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
const Microsoft.Identity.Client.MsalError.MtlsPopNotSupportedForEnvironment = "mtls_pop_not_supported_for_environment" -> string
23 changes: 23 additions & 0 deletions test_results.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Build started 01/29/2026 14:27:45.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this file needed? Remove?

1>Project "/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/tests/Microsoft.Identity.Test.Unit/Microsoft.Identity.Test.Unit.csproj" on node 1 (Restore target(s)).
1>_GetAllRestoreProjectPathItems:
Determining projects to restore...
1>Project "/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/tests/Microsoft.Identity.Test.Unit/Microsoft.Identity.Test.Unit.csproj" (1) is building "/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj" (2:8) on node 1 (_GenerateProjectRestoreGraph target(s)).
2:8>Project "/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj" (2:8) is building "/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj" (2:12) on node 1 (_GenerateProjectRestoreGraphPerFramework target(s)).
2>/usr/share/dotnet/sdk/8.0.417/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(90,5): error NETSDK1100: To build a project targeting Windows on this operating system, set the EnableWindowsTargeting property to true. [/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj::TargetFramework=netcoreapp3.1]
2>Done Building Project "/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj" (_GenerateProjectRestoreGraphPerFramework target(s)) -- FAILED.
2>Done Building Project "/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj" (_GenerateProjectRestoreGraph target(s)) -- FAILED.
1>Done Building Project "/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/tests/Microsoft.Identity.Test.Unit/Microsoft.Identity.Test.Unit.csproj" (Restore target(s)) -- FAILED.

Build FAILED.

"/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/tests/Microsoft.Identity.Test.Unit/Microsoft.Identity.Test.Unit.csproj" (Restore target) (1) ->
"/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj" (_GenerateProjectRestoreGraph target) (2:8) ->
"/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj" (_GenerateProjectRestoreGraphPerFramework target) (2:12) ->
(ProcessFrameworkReferences target) ->
/usr/share/dotnet/sdk/8.0.417/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(90,5): error NETSDK1100: To build a project targeting Windows on this operating system, set the EnableWindowsTargeting property to true. [/home/runner/work/microsoft-authentication-library-for-dotnet/microsoft-authentication-library-for-dotnet/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj::TargetFramework=netcoreapp3.1]

0 Warning(s)
1 Error(s)

Time Elapsed 00:00:01.61
Original file line number Diff line number Diff line change
Expand Up @@ -669,9 +669,7 @@ public async Task MtlsPop_ValidateExpectedUrlAsync()
[DataTestMethod]
[DataRow("login.microsoftonline.com", "mtlsauth.microsoft.com")]
[DataRow("login.microsoftonline.us", "mtlsauth.microsoftonline.us")]
[DataRow("login.usgovcloudapi.net", "mtlsauth.microsoftonline.us")]
[DataRow("login.partner.microsoftonline.cn", "mtlsauth.partner.microsoftonline.cn")]
[DataRow("login.chinacloudapi.cn", "mtlsauth.partner.microsoftonline.cn")]
[DataRow("login.sovcloud-identity.fr", "mtlsauth.sovcloud-identity.fr")]
[DataRow("login.sovcloud-identity.de", "mtlsauth.sovcloud-identity.de")]
[DataRow("login.sovcloud-identity.sg", "mtlsauth.sovcloud-identity.sg")]
Expand Down Expand Up @@ -732,6 +730,81 @@ public async Task PublicAndSovereignCloud_UsesPreferredNetwork_AndNoDiscovery_As
}
}

[DataTestMethod]
[DataRow("login.usgovcloudapi.net", MsalErrorMessage.MtlsPopNotSupportedForUsGovCloudApiMessage)]
[DataRow("login.chinacloudapi.cn", MsalErrorMessage.MtlsPopNotSupportedForChinaCloudApiMessage)]
public async Task UnsupportedSovereignHosts_ThrowsMsalClientException_Async(string unsupportedHost, string expectedErrorMessage)
{
// Arrange
string authorityUrl = $"https://{unsupportedHost}/17b189bc-2b81-4ec5-aa51-3e628cbc931b";

using (var envContext = new EnvVariableContext())
{
Environment.SetEnvironmentVariable("REGION_NAME", EastUsRegion);

using (var harness = new MockHttpAndServiceBundle())
{
var app = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithAuthority(authorityUrl)
.WithHttpManager(harness.HttpManager)
.WithAzureRegion(ConfidentialClientApplication.AttemptRegionDiscovery)
.WithCertificate(s_testCertificate)
.Build();

// Act & Assert
var exception = await Assert.ThrowsExceptionAsync<MsalClientException>(async () =>
{
await app.AcquireTokenForClient(TestConstants.s_scope)
.WithMtlsProofOfPossession()
.ExecuteAsync()
.ConfigureAwait(false);
}).ConfigureAwait(false);

Assert.AreEqual(MsalError.MtlsPopNotSupportedForEnvironment, exception.ErrorCode);
Assert.AreEqual(expectedErrorMessage, exception.Message);
}
}
}

[DataTestMethod]
[DataRow("mtlsauth.microsoft.com")]
[DataRow("sts.windows.net")]
[DataRow("graph.microsoft.com")]
public async Task NonLoginHosts_ThrowsMsalClientException_Async(string nonLoginHost)
{
// Arrange
string authorityUrl = $"https://{nonLoginHost}/17b189bc-2b81-4ec5-aa51-3e628cbc931b";

using (var envContext = new EnvVariableContext())
{
Environment.SetEnvironmentVariable("REGION_NAME", EastUsRegion);

using (var harness = new MockHttpAndServiceBundle())
{
var app = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithAuthority(authorityUrl)
.WithHttpManager(harness.HttpManager)
.WithAzureRegion(ConfidentialClientApplication.AttemptRegionDiscovery)
.WithCertificate(s_testCertificate)
.Build();

// Act & Assert
var exception = await Assert.ThrowsExceptionAsync<MsalClientException>(async () =>
{
await app.AcquireTokenForClient(TestConstants.s_scope)
.WithMtlsProofOfPossession()
.ExecuteAsync()
.ConfigureAwait(false);
}).ConfigureAwait(false);

Assert.AreEqual(MsalError.MtlsPopNotSupportedForEnvironment, exception.ErrorCode);
Assert.AreEqual(MsalErrorMessage.MtlsPopNotSupportedForNonLoginHostMessage, exception.Message);
}
}
}

[TestMethod]
public async Task AcquireTokenForClient_WithMtlsPop_NonStandardCloudAsync()
{
Expand Down