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
2 changes: 2 additions & 0 deletions sdk/identity/Azure.Identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions sdk/identity/Azure.Identity/src/TenantIdResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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");
}
Expand Down
59 changes: 59 additions & 0 deletions sdk/identity/Azure.Identity/tests/TenantIdResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>(), tenantId: upperCaseTenantId);
var contextWithLowerCase = new TokenRequestContext(Array.Empty<string>(), tenantId: lowerCaseTenantId);
var contextWithMixedCase = new TokenRequestContext(Array.Empty<string>(), 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<string>(), 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<string>(), 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);
}
}
}