Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -140,7 +140,7 @@ private static void InitMtlsPopParameters(
if (serviceBundle.Config.Authority.AuthorityInfo.AuthorityType == AuthorityType.Aad)
{
string tenant = AuthorityInfo.GetFirstPathSegment(serviceBundle.Config.Authority.AuthorityInfo.CanonicalAuthority);
if (AadAuthority.IsCommonOrganizationsOrConsumersTenant(tenant))
if (AadAuthority.IsCommonOrganizationsOrConsumersTenant(tenant) && !AadAuthority.IsConsumersGuid(tenant))
Comment thread
Robbie-Microsoft marked this conversation as resolved.
Outdated
Comment thread
Robbie-Microsoft marked this conversation as resolved.
Outdated
{
throw new MsalClientException(
MsalError.MissingTenantedAuthority,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,10 @@ public static IAuthorityValidator CreateAuthorityValidator(AuthorityInfo authori
/// - if the authority is not defined at the application level and the request level is not AAD, use the request authority
/// - if the authority is defined at app level, and the request level authority is of different type, throw an exception
///
/// - if the intended authority is consumers, please define it at the app level and not at the request level.
/// - if the intended authority is the "consumers" alias, please define it at the app level and not at the request level.
/// known issue: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2929
/// - if the intended authority is the MSA tenant GUID (9188040d-6c67-4c5b-b112-36a304b66dad), it IS honored
/// at the request level because it is a specific tenant ID rather than a tenantless alias.
/// </summary>
public static async Task<Authority> CreateAuthorityForRequestAsync(RequestContext requestContext,
AuthorityInfo requestAuthorityInfo,
Expand Down Expand Up @@ -566,6 +568,7 @@ public static async Task<Authority> CreateAuthorityForRequestAsync(RequestContex
new AadAuthority(CreateAuthorityWithEnvironment(requestAuthorityInfo, account?.Environment).AuthorityInfo) :
new AadAuthority(requestAuthorityInfo);
if (!requestAuthority.IsCommonOrganizationsOrConsumersTenant() ||
requestAuthority.IsConsumersGuid() ||
requestAuthority.IsOrganizationsTenantWithMsaPassthroughEnabled(requestContext.ServiceBundle.Config.IsBrokerEnabled && requestContext.ServiceBundle.Config.BrokerOptions != null && requestContext.ServiceBundle.Config.BrokerOptions.MsaPassthrough, account?.HomeAccountId?.TenantId))
{
return requestAuthority;
Expand Down
18 changes: 17 additions & 1 deletion src/client/Microsoft.Identity.Client/Instance/AadAuthority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ internal static bool IsCommonOrganizationsOrConsumersTenant(string tenantId)
(IsCommonOrOrganizationsTenant(tenantId) || IsConsumers(tenantId));
}

/// <summary>
/// Returns true only when the tenant is the MSA GUID (9188040d-6c67-4c5b-b112-36a304b66dad),
/// as distinct from the "consumers" string alias. The GUID is a real tenant ID and should be
/// honored when specified explicitly (e.g. via WithTenantId at request level).
/// </summary>
internal bool IsConsumersGuid()
Comment thread
Robbie-Microsoft marked this conversation as resolved.
Outdated
{
return IsConsumersGuid(TenantId);
}

internal static bool IsConsumersGuid(string tenantId)
{
return !string.IsNullOrEmpty(tenantId) &&
tenantId.Equals(Constants.MsaTenantId, StringComparison.OrdinalIgnoreCase);
}

internal bool IsOrganizationsTenantWithMsaPassthroughEnabled(bool isMsaPassthrough, string accountTenantId)
{
return accountTenantId!= null && isMsaPassthrough && TenantId.Equals(Constants.Organizations, StringComparison.OrdinalIgnoreCase) &&
Expand All @@ -83,7 +99,7 @@ internal static bool IsCommonOrOrganizationsTenant(string tenantId)
internal override string GetTenantedAuthority(string tenantId, bool forceSpecifiedTenant = false)
{
if (!string.IsNullOrEmpty(tenantId) &&
(forceSpecifiedTenant || IsCommonOrganizationsOrConsumersTenant()))
(forceSpecifiedTenant || (IsCommonOrganizationsOrConsumersTenant() && !IsConsumersGuid())))
{
var authorityUri = AuthorityInfo.CanonicalAuthority;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,9 +578,11 @@ private static void FilterTokensByHomeAccountTenantOrAssertion(
// OBO calls FindAccessTokenAsync directly, but we are not able to resolve the authority
// unless the developer has configured a tenanted authority. If they have configured /common
// then we cannot filter by tenant and will use whatever is in the cache.
// The MSA GUID (9188040d-...) is a real tenant ID and should be filtered normally.
filterByTenantId =
!string.IsNullOrEmpty(requestTenantId) &&
!AadAuthority.IsCommonOrganizationsOrConsumersTenant(requestTenantId);
(!AadAuthority.IsCommonOrganizationsOrConsumersTenant(requestTenantId) ||
AadAuthority.IsConsumersGuid(requestTenantId));
}

if (filterByTenantId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public class AuthorityTests : TestBase
private static readonly Authority s_b2cAuthority = Authority.CreateAuthority(TestConstants.B2CAuthority, true);
private static readonly Authority s_commonNetAuthority = Authority.CreateAuthority(TestConstants.PrefCacheAuthorityCommonTenant, true);

private static readonly Authority s_consumersTenantAuthority =
Authority.CreateAuthority(TestConstants.AuthorityConsumersTenant, true);
private static readonly Authority s_consumerTidAuthority =
Authority.CreateAuthority(TestConstants.AuthorityConsumerTidTenant, true);

private MockHttpAndServiceBundle _harness;
private RequestContext _testRequestContext;

Expand Down Expand Up @@ -453,6 +458,56 @@ public void VerifyConfigAuthorityType(string authorityHost, Type authorityTypeIn
Assert.AreEqual(app.AuthorityInfo.AuthorityType.ToString(), authorityType);
}

/// <summary>
/// Regression test for https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/5951
/// When WithTenantId is called with the MSA GUID at request level, it should be honored regardless
/// of the app-level authority, because the GUID is a real tenant ID (not a tenantless alias).
/// The "consumers" string alias is a different case and is still ignored at request level.
/// </summary>
[TestMethod]
public void WithTenantId_ConsumerGuid_IsHonoredAtRequestLevel()
Comment thread
Robbie-Microsoft marked this conversation as resolved.
Outdated
{
// Case 1 (bug regression): app=specific tenant, request=MSA GUID → MSA GUID wins
VerifyAuthority(
configAuthority: s_utidAuthority,
requestAuthority: s_consumerTidAuthority,
account: null,
expectedTenantId: TestConstants.MsaTenantId,
_testRequestContext);

// Case 2: app=common, request=MSA GUID → MSA GUID wins
VerifyAuthority(
configAuthority: s_commonAuthority,
requestAuthority: s_consumerTidAuthority,
account: null,
expectedTenantId: TestConstants.MsaTenantId,
_testRequestContext);

// Case 3: app=specific tenant, request="consumers" string alias → app tenant wins (existing behavior preserved)
VerifyAuthority(
configAuthority: s_utidAuthority,
requestAuthority: s_consumersTenantAuthority,
account: null,
expectedTenantId: TestConstants.Utid,
_testRequestContext);

// Case 4: app=common, request="consumers" string alias → "common" used (existing behavior preserved)
VerifyAuthority(
configAuthority: s_commonAuthority,
requestAuthority: s_consumersTenantAuthority,
account: null,
expectedTenantId: "common",
_testRequestContext);

// Case 5: app=MSA GUID, no request override → MSA GUID used (no regression)
VerifyAuthority(
configAuthority: s_consumerTidAuthority,
requestAuthority: null,
account: null,
expectedTenantId: TestConstants.MsaTenantId,
_testRequestContext);
}

private static void VerifyAuthority(
Authority configAuthority,
Authority requestAuthority,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,25 @@ public void CreateAuthorityFromTenantedWithTenantTest()
Assert.AreEqual("https://login.microsoft.com/other_tenant_id/", updatedAuthority2, "Changed with forced flag");
}

[TestMethod]
public void GetTenantedAuthority_MsaGuid_IsNotReplaced()
{
// Regression test for https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/5951
// The MSA GUID is a real tenant ID — GetTenantedAuthority should NOT replace it
// unless forceSpecifiedTenant=true.
Authority authority = Authority.CreateAuthority(TestConstants.AuthorityConsumerTidTenant);

// Without force: MSA GUID is preserved (it's a real tenant)
string updatedAuthority = authority.GetTenantedAuthority("other_tenant_id", false);
Assert.AreEqual(TestConstants.AuthorityConsumerTidTenant, updatedAuthority,
"MSA GUID authority should not be replaced when forceSpecifiedTenant=false");

// With force: replacement is honored
string updatedAuthority2 = authority.GetTenantedAuthority("other_tenant_id", true);
StringAssert.Contains(updatedAuthority2, "other_tenant_id",
"MSA GUID authority should be replaced when forceSpecifiedTenant=true");
}

[TestMethod]
public void CreateAuthorityFromCommonWithTenantTest()
{
Expand Down
42 changes: 42 additions & 0 deletions tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsPopTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,48 @@ await app.AcquireTokenForClient(TestConstants.s_scope)
}
}

[TestMethod]
public async Task MtlsPop_WithMsaTenantGuidAuthority_DoesNotThrowMissingTenantedAuthorityAsync()
Comment thread
Robbie-Microsoft marked this conversation as resolved.
Outdated
{
// Regression test: the MSA tenant GUID (9188040d-...) is a real tenant ID, not a tenantless alias.
// Configuring an app with it at the app level and using mTLS PoP should NOT throw MissingTenantedAuthority.
// See https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/5951
const string region = "eastus";

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

using (var httpManager = new MockHttpManager())
{
var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
.WithCertificate(s_testCertificate)
.WithAuthority(TestConstants.AuthorityConsumerTidTenant)
.WithAzureRegion(ConfidentialClientApplication.AttemptRegionDiscovery)
.WithHttpManager(httpManager)
.BuildConcrete();

// Should NOT throw MissingTenantedAuthority — the MSA GUID is a tenanted authority.
// Any other exception (e.g. MsalServiceException from the network call) is acceptable.
try
{
await app.AcquireTokenForClient(TestConstants.s_scope)
.WithMtlsProofOfPossession()
.ExecuteAsync()
.ConfigureAwait(false);
}
catch (MsalClientException ex) when (ex.ErrorCode == MsalError.MissingTenantedAuthority)
{
Assert.Fail("MSA tenant GUID should not be rejected as a non-tenanted authority");
}
catch (Exception)
{
// Any other exception means we passed the MissingTenantedAuthority guard — test passes.
}
}
}
}

[TestMethod]
public async Task MtlsPop_ValidateExpectedUrlAsync()
{
Expand Down
Loading