Fix case-sensitive tenant ID comparison and introduce StringComparisons helper#2907
Fix case-sensitive tenant ID comparison and introduce StringComparisons helper#2907tmeschter wants to merge 7 commits into
Conversation
…ialProvider Azure JWT 'tid' claims are lowercase GUIDs. When the configured tenant is supplied in mixed case, the ordinal '!=' comparison rejected valid requests from the correct tenant. Use OrdinalIgnoreCase so casing differences are treated as the same tenant. Fixes microsoft#407 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce a StringComparisons type with a static TenantId property that returns StringComparison.OrdinalIgnoreCase, and use it in HttpOnBehalfOfTokenCredentialProvider so the intended comparison semantics are explicit at the call site. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace literal StringComparison.OrdinalIgnoreCase usages with the StringComparisons.TenantId helper in TenantService and the AzureIsv test skip guard, keeping tenant ID comparison semantics consistent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce SubscriptionId and SubscriptionDisplayName properties on StringComparisons and use them at the subscription lookup call sites in SubscriptionService and SubscriptionListCommand. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…parisons Introduce a ResourceGroup property on StringComparisons and use it at the resource group name comparison call sites across ResourceGroupService, AppLensService, AzureBackupService, ResourceResolverService, and FoundryExtensionsService. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce a ResourceName property on StringComparisons and use it at the Azure resource name lookup call sites across Monitor, Cosmos, EventGrid, Search, Compute, FileShares, and FoundryExtensions services. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes an authentication edge case in the MCP core by making tenant ID (tid) comparisons case-insensitive for HTTP On-Behalf-Of token acquisition, and introduces a StringComparisons helper to standardize/document string comparison semantics across Azure identifiers.
Changes:
- Fixes tenant ID mismatch checks in
HttpOnBehalfOfTokenCredentialProviderby using a case-insensitive comparison. - Adds
Microsoft.Mcp.Core.Helpers.StringComparisonsto centralize intendedStringComparisonusage for common Azure identifiers. - Migrates multiple existing
OrdinalIgnoreCasecomparisons to use the helper for clarity and consistency.
Show a summary per file
| File | Description |
|---|---|
| tools/Azure.Mcp.Tools.Search/src/Services/SearchService.cs | Uses StringComparisons.ResourceName when matching knowledge base names. |
| tools/Azure.Mcp.Tools.Monitor/src/Services/ResourceResolverService.cs | Uses StringComparisons.ResourceName/ResourceGroup for resource filtering. |
| tools/Azure.Mcp.Tools.Monitor/src/Services/MonitorService.cs | Uses StringComparisons.ResourceName for workspace name matching. |
| tools/Azure.Mcp.Tools.FoundryExtensions/src/Services/FoundryExtensionsService.cs | Uses StringComparisons.ResourceName/ResourceGroup for index/resource matching. |
| tools/Azure.Mcp.Tools.FileShares/src/Services/FileSharesService.cs | Uses StringComparisons.ResourceName for snapshot name matching. |
| tools/Azure.Mcp.Tools.EventGrid/src/Services/EventGridService.cs | Uses StringComparisons.ResourceName for topic/system topic name matching. |
| tools/Azure.Mcp.Tools.Cosmos/src/Services/CosmosService.cs | Uses StringComparisons.ResourceName for Cosmos account name matching. |
| tools/Azure.Mcp.Tools.Compute/src/Commands/Disk/DiskUpdateCommand.cs | Uses StringComparisons.ResourceName when selecting disks by name. |
| tools/Azure.Mcp.Tools.AzureIsv/tests/Azure.Mcp.Tools.AzureIsv.Tests/AzureIsvCommandTests.cs | Uses StringComparisons.TenantId for tenant gating logic in tests. |
| tools/Azure.Mcp.Tools.AzureBackup/src/Services/AzureBackupService.cs | Uses StringComparisons.ResourceGroup for resource group filtering. |
| tools/Azure.Mcp.Tools.AppLens/src/Services/AppLensService.cs | Uses StringComparisons.ResourceGroup for resource group filtering. |
| core/Microsoft.Mcp.Core/src/Services/Azure/Authentication/HttpOnBehalfOfTokenCredentialProvider.cs | Fixes tenant ID comparison to be case-insensitive via StringComparisons.TenantId. |
| core/Microsoft.Mcp.Core/src/Helpers/StringComparisons.cs | Introduces centralized comparison constants for tenant/subscription/resource identifiers. |
| core/Azure.Mcp.Core/src/Services/Azure/Tenant/TenantService.cs | Uses StringComparisons.TenantId for tenant ID lookup comparisons. |
| core/Azure.Mcp.Core/src/Services/Azure/Subscription/SubscriptionService.cs | Uses StringComparisons.SubscriptionId/SubscriptionDisplayName for subscription lookup comparisons. |
| core/Azure.Mcp.Core/src/Services/Azure/ResourceGroup/ResourceGroupService.cs | Uses StringComparisons.ResourceGroup for cached RG name lookup. |
| core/Azure.Mcp.Core/src/Areas/Subscription/Commands/SubscriptionListCommand.cs | Uses StringComparisons.SubscriptionId when identifying default subscription. |
Copilot's findings
Comments suppressed due to low confidence (1)
core/Microsoft.Mcp.Core/src/Services/Azure/Authentication/HttpOnBehalfOfTokenCredentialProvider.cs:45
- The tenant ID comparison behavior in HttpOnBehalfOfTokenCredentialProvider changed from case-sensitive to case-insensitive, but there are no unit tests covering this guard. Adding a focused test would prevent regressions (e.g., mixed-case tenantId should not throw; genuinely different tenantId should throw InvalidOperationException).
if (tenantId is not null)
{
if (httpContext.User.FindFirst("tid")?.Value is string tidClaim
&& !string.Equals(tidClaim, tenantId, StringComparisons.TenantId))
{
_logger.LogWarning(
"The requested token tenant '{GetTokenTenant}' does not match the tenant of the authenticated user '{TidClaim}'. Going to throw.",
tenantId,
tidClaim);
throw new InvalidOperationException(
$"The requested token tenant '{tenantId}' does not match the tenant of the authenticated user '{tidClaim}'.");
- Files reviewed: 17/17 changed files
- Comments generated: 0
| /// well-known string values, making the intended comparison semantics explicit | ||
| /// at each call site. | ||
| /// </summary> | ||
| public static class StringComparisons |
There was a problem hiding this comment.
The point of this type is to make it clear at the call site (string.Equals(...), etc.) that the expected comparison is being used. Also, if we're using the wrong comparison we can change it here to update it for all consumers.
I didn't move all usages of StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase to this file in order to keep the size of the change manageable. More can be added in a future update.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| /// The comparison to use when matching an Azure resource by its name. Azure | ||
| /// resource names are matched case-insensitively for lookup purposes. | ||
| /// </summary> | ||
| public static StringComparison ResourceName => StringComparison.OrdinalIgnoreCase; |
There was a problem hiding this comment.
Are Azure resources always a case-insensitive comparison? This is the only one that gives me concern as it's a broad category.
There was a problem hiding this comment.
We may be doing case insensitive lookup/queries but I don't think we can assume two names differ only in case refer to the same resource.
https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules
Summary
Fixes #407: the tenant ID comparison in
HttpOnBehalfOfTokenCredentialProviderused an ordinal, case-sensitive!=, so a valid tenant supplied in mixed case (e.g. from config) was rejected even though the JWTtidclaim (a lowercase GUID) referred to the same tenant.The fix makes the comparison case-insensitive, then generalizes the pattern by introducing a small
StringComparisonshelper that documents the intended comparison semantics at each call site.Changes
HttpOnBehalfOfTokenCredentialProvidernow compares thetidclaim and configured tenant case-insensitively.StringComparisonshelper (core/Microsoft.Mcp.Core/src/Helpers/StringComparisons.cs) with properties that make comparison intent explicit:TenantIdSubscriptionId,SubscriptionDisplayNameResourceGroupResourceNameOrdinalIgnoreCase):TenantService, AzureIsv test guardSubscriptionService,SubscriptionListCommandResourceGroupService,AppLensService,AzureBackupService,ResourceResolverService,FoundryExtensionsServiceMonitor,Cosmos,EventGrid,Search,Compute,FileShares,FoundryExtensionsValidation
dotnet buildsucceeds for all affected projects (0 warnings, 0 errors).Invoking Livetests
Copilot submitted PRs are not trustworthy by default. Users with
writeaccess to the repo need to validate the contents of this PR before leaving a comment with the text/azp run mcp - pullrequest - live. This will trigger the necessary livetest workflows to complete required validation.