Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ internal bool IsCommonOrganizationsOrConsumersTenant()

internal static bool IsCommonOrganizationsOrConsumersTenant(string tenantId)
Comment thread
Robbie-Microsoft marked this conversation as resolved.
Outdated
{
// The MSA GUID (9188040d-...) is a real tenant — only the "consumers" string alias is tenantless.
return !string.IsNullOrEmpty(tenantId) &&
(IsCommonOrOrganizationsTenant(tenantId) || IsConsumers(tenantId));
(IsCommonOrOrganizationsTenant(tenantId) ||
tenantId.Equals(Constants.Consumers, StringComparison.OrdinalIgnoreCase));
}

internal bool IsOrganizationsTenantWithMsaPassthroughEnabled(bool isMsaPassthrough, string accountTenantId)
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,41 @@ 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]
[DataRow(TestConstants.AuthorityUtidTenant, TestConstants.AuthorityConsumerTidTenant, TestConstants.MsaTenantId,
DisplayName = "AppSpecificTenant_RequestMsaGuid_MsaGuidWins")]
[DataRow(TestConstants.AuthorityCommonTenant, TestConstants.AuthorityConsumerTidTenant, TestConstants.MsaTenantId,
DisplayName = "AppCommon_RequestMsaGuid_MsaGuidWins")]
[DataRow(TestConstants.AuthorityUtidTenant, TestConstants.AuthorityConsumersTenant, TestConstants.Utid,
DisplayName = "AppSpecificTenant_RequestConsumersAlias_AppTenantWins")]
[DataRow(TestConstants.AuthorityCommonTenant, TestConstants.AuthorityConsumersTenant, "common",
DisplayName = "AppCommon_RequestConsumersAlias_CommonUsed")]
[DataRow(TestConstants.AuthorityConsumerTidTenant, null, TestConstants.MsaTenantId,
DisplayName = "AppMsaGuid_NoRequestOverride_MsaGuidUsed")]
public void WithTenantId_ConsumerGuid_IsHonoredAtRequestLevel(
string configAuthorityUrl,
string requestAuthorityUrl,
string expectedTenantId)
{
var configAuthority = Authority.CreateAuthority(configAuthorityUrl, true);
Authority requestAuthority = requestAuthorityUrl == null
? null
: Authority.CreateAuthority(requestAuthorityUrl, true);

VerifyAuthority(
configAuthority: configAuthority,
requestAuthority: requestAuthority,
account: null,
expectedTenantId: expectedTenantId,
_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 Expand Up @@ -361,6 +380,20 @@ public void IsCommonOrOrganizationsTenantTest()
Assert.IsFalse(aadAuthorityInstance.IsCommonOrOrganizationsTenant());
}

[TestMethod]
public void IsCommonOrganizationsOrConsumersTenant_MsaGuid_ReturnsFalse()
{
// Regression test for https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/5951
// The MSA GUID is a real tenant, not a tenantless alias — it must NOT match the tenantless predicate.
Assert.IsTrue(AadAuthority.IsCommonOrganizationsOrConsumersTenant("common"));
Assert.IsTrue(AadAuthority.IsCommonOrganizationsOrConsumersTenant("organizations"));
Assert.IsTrue(AadAuthority.IsCommonOrganizationsOrConsumersTenant("consumers"));
Assert.IsFalse(AadAuthority.IsCommonOrganizationsOrConsumersTenant(TestConstants.MsaTenantId),
"MSA GUID should not be treated as a tenantless authority");
Assert.IsFalse(AadAuthority.IsCommonOrganizationsOrConsumersTenant("some-real-tenant-guid"),
"Any real tenant GUID should not be treated as a tenantless authority");
}

[TestMethod]
public async Task CreateAuthorityForRequestAsync_MSAPassthroughAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,49 @@ public async Task ClientCreds_MustFilterByTenantId_Async()
}
}

[TestMethod]
[TestCategory(TestCategories.Regression)]
[WorkItem(5951)] // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/5951
public async Task ClientCreds_WithTenantId_MsaGuid_UsesCorrectTokenEndpoint_Async()
Comment thread
Robbie-Microsoft marked this conversation as resolved.
{
// Regression test: the MSA tenant GUID (9188040d-...) is a real tenant.
// App is configured with the MSA GUID authority at app level. A request-level
// .WithTenantId() override with a different tenant must be honored — the token
// request must target the override tenant endpoint, not silently use the MSA GUID.
string expectedTokenEndpoint =
$"https://login.microsoftonline.com/{TestConstants.Utid}/oauth2/v2.0/token";

using (var httpManager = new MockHttpManager())
{
httpManager.AddInstanceDiscoveryMockHandler();

var tokenHandler = new MockHttpMessageHandler
{
ExpectedUrl = expectedTokenEndpoint,
ExpectedMethod = HttpMethod.Post,
ResponseMessage = MockHelpers.CreateSuccessfulClientCredentialTokenResponseMessage()
};
httpManager.AddMockHandler(tokenHandler);

ConfidentialClientApplication app =
ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
.WithClientSecret(TestConstants.ClientSecret)
.WithAuthority(TestConstants.AuthorityConsumerTidTenant)
.WithHttpManager(httpManager)
.BuildConcrete();

var result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray())
.WithTenantId(TestConstants.Utid)
.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);

Assert.IsNotNull(result.AccessToken);
// The cache entry should be keyed on the override tenant, not the MSA GUID
Assert.AreEqual(TestConstants.Utid,
app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().Single().TenantId,
StringComparer.OrdinalIgnoreCase);
}
}

[TestMethod]
public async Task ClientCreds_UsesDefaultPartitionedCacheCorrectly_Async()
{
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 @@ -601,6 +601,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