diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 92dce55384e5..c530fbea10b9 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -16,6 +16,8 @@ ### Bugs Fixed +- Tenant ID comparisons in credential options are now case-insensitive. This affects `AdditionallyAllowedTenants` values which will now be matched against tenant IDs without case sensitivity, making the authentication more resilient to case differences in tenant IDs returned from WWW-Authenticate challenges ([#51693](https://github.com/Azure/azure-sdk-for-net/issues/51693)). + ### Other Changes ## 1.15.0-beta.1 (2025-07-17) diff --git a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs index b714c0cfeb8b..a69679496c75 100644 --- a/sdk/identity/Azure.Identity/src/TenantIdResolver.cs +++ b/sdk/identity/Azure.Identity/src/TenantIdResolver.cs @@ -21,9 +21,9 @@ public override string Resolve(string explicitTenantId, TokenRequestContext cont { bool disableMultiTenantAuth = IdentityCompatSwitches.DisableTenantDiscovery; - if (context.TenantId != explicitTenantId && context.TenantId != null && explicitTenantId != null) + if (!string.Equals(context.TenantId, explicitTenantId, StringComparison.OrdinalIgnoreCase) && context.TenantId != null && explicitTenantId != null) { - if (disableMultiTenantAuth || explicitTenantId == Constants.AdfsTenantId) + if (disableMultiTenantAuth || string.Equals(explicitTenantId, Constants.AdfsTenantId, StringComparison.OrdinalIgnoreCase)) { AzureIdentityEventSource.Singleton.TenantIdDiscoveredAndNotUsed(explicitTenantId, context.TenantId); } @@ -36,11 +36,11 @@ public override string Resolve(string explicitTenantId, TokenRequestContext cont string resolvedTenantId = disableMultiTenantAuth switch { true => explicitTenantId, - false when explicitTenantId == Constants.AdfsTenantId => explicitTenantId, + false when string.Equals(explicitTenantId, Constants.AdfsTenantId, StringComparison.OrdinalIgnoreCase) => explicitTenantId, _ => context.TenantId ?? explicitTenantId }; - if (explicitTenantId != null && resolvedTenantId != explicitTenantId && additionallyAllowedTenantIds != AllTenants && Array.BinarySearch(additionallyAllowedTenantIds, resolvedTenantId, StringComparer.OrdinalIgnoreCase) < 0) + if (explicitTenantId != null && !string.Equals(resolvedTenantId, explicitTenantId, StringComparison.OrdinalIgnoreCase) && additionallyAllowedTenantIds != AllTenants && Array.BinarySearch(additionallyAllowedTenantIds, resolvedTenantId, StringComparer.OrdinalIgnoreCase) < 0) { throw new AuthenticationFailedException($"The current credential is not configured to acquire tokens for tenant {resolvedTenantId}. To enable acquiring tokens for this tenant add it to the AdditionallyAllowedTenants on the credential options, or add \"*\" to AdditionallyAllowedTenants to allow acquiring tokens for any tenant. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/multitenant/troubleshoot"); } diff --git a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs index 9f33438ceec6..d515259b572a 100644 --- a/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs +++ b/sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs @@ -109,5 +109,64 @@ public static void AssertAllowedTenantIdsEnforcedAsync(string tenantId, TokenReq StringAssert.Contains($"The current credential is not configured to acquire tokens for tenant {tokenRequestContext.TenantId}", ex.Message); } } + + [Test] + public void ResolveWithCaseInsensitiveTenantIdComparison() + { + const string upperCaseTenantId = "CLIENT-TENANT"; + const string lowerCaseTenantId = "client-tenant"; + const string mixedCaseTenantId = "Client-Tenant"; + + var contextWithUpperCase = new TokenRequestContext(Array.Empty(), tenantId: upperCaseTenantId); + var contextWithLowerCase = new TokenRequestContext(Array.Empty(), tenantId: lowerCaseTenantId); + var contextWithMixedCase = new TokenRequestContext(Array.Empty(), tenantId: mixedCaseTenantId); + + // Test that different case variations of the same tenant ID are considered equal + var result1 = TenantIdResolverBase.Default.Resolve(lowerCaseTenantId, contextWithUpperCase, TenantIdResolverBase.AllTenants); + var result2 = TenantIdResolverBase.Default.Resolve(upperCaseTenantId, contextWithLowerCase, TenantIdResolverBase.AllTenants); + var result3 = TenantIdResolverBase.Default.Resolve(mixedCaseTenantId, contextWithLowerCase, TenantIdResolverBase.AllTenants); + + // All should resolve to the context tenant ID as that takes precedence + Assert.AreEqual(upperCaseTenantId, result1); + Assert.AreEqual(lowerCaseTenantId, result2); + Assert.AreEqual(lowerCaseTenantId, result3); + } + + [Test] + public void ResolveWithCaseInsensitiveAdfsTenantId() + { + const string upperCaseAdfs = "ADFS"; + const string mixedCaseAdfs = "Adfs"; + const string lowerCaseAdfs = "adfs"; + + var contextWithHint = new TokenRequestContext(Array.Empty(), tenantId: "some-hint"); + + // Test that different case variations of ADFS are all recognized + var result1 = TenantIdResolverBase.Default.Resolve(upperCaseAdfs, contextWithHint, TenantIdResolverBase.AllTenants); + var result2 = TenantIdResolverBase.Default.Resolve(mixedCaseAdfs, contextWithHint, TenantIdResolverBase.AllTenants); + var result3 = TenantIdResolverBase.Default.Resolve(lowerCaseAdfs, contextWithHint, TenantIdResolverBase.AllTenants); + + // For ADFS, the explicit tenant ID should be returned regardless of case + Assert.AreEqual(upperCaseAdfs, result1); + Assert.AreEqual(mixedCaseAdfs, result2); + Assert.AreEqual(lowerCaseAdfs, result3); + } + + [Test] + public void ResolveWithCaseInsensitiveComparisonForAllowedTenants() + { + const string explicitTenantId = "explicit-tenant"; + const string upperCaseContextTenant = "CONTEXT-TENANT"; + const string lowerCaseContextTenant = "context-tenant"; + + var contextWithUpperCase = new TokenRequestContext(Array.Empty(), tenantId: upperCaseContextTenant); + var additionallyAllowedTenants = new[] { lowerCaseContextTenant }; + + // The context tenant ID (uppercase) should be allowed because it matches + // the additionally allowed tenant (lowercase) in a case-insensitive manner + var result = TenantIdResolverBase.Default.Resolve(explicitTenantId, contextWithUpperCase, additionallyAllowedTenants); + + Assert.AreEqual(upperCaseContextTenant, result); + } } }