Skip to content

Fix case-sensitive tenant ID comparison and introduce StringComparisons helper#2907

Open
tmeschter wants to merge 7 commits into
microsoft:mainfrom
tmeschter:260616-StringComparers
Open

Fix case-sensitive tenant ID comparison and introduce StringComparisons helper#2907
tmeschter wants to merge 7 commits into
microsoft:mainfrom
tmeschter:260616-StringComparers

Conversation

@tmeschter

Copy link
Copy Markdown
Member

Summary

Fixes #407: the tenant ID comparison in HttpOnBehalfOfTokenCredentialProvider used an ordinal, case-sensitive !=, so a valid tenant supplied in mixed case (e.g. from config) was rejected even though the JWT tid claim (a lowercase GUID) referred to the same tenant.

The fix makes the comparison case-insensitive, then generalizes the pattern by introducing a small StringComparisons helper that documents the intended comparison semantics at each call site.

Changes

  • Fix Update MCP server logos in README.md #407: HttpOnBehalfOfTokenCredentialProvider now compares the tid claim and configured tenant case-insensitively.
  • New StringComparisons helper (core/Microsoft.Mcp.Core/src/Helpers/StringComparisons.cs) with properties that make comparison intent explicit:
    • TenantId
    • SubscriptionId, SubscriptionDisplayName
    • ResourceGroup
    • ResourceName
  • Migrated existing comparisons to use the helper (behavior unchanged — all were already OrdinalIgnoreCase):
    • Tenant ID: TenantService, AzureIsv test guard
    • Subscription: SubscriptionService, SubscriptionListCommand
    • Resource group: ResourceGroupService, AppLensService, AzureBackupService, ResourceResolverService, FoundryExtensionsService
    • Resource name: Monitor, Cosmos, EventGrid, Search, Compute, FileShares, FoundryExtensions

Validation

  • dotnet build succeeds for all affected projects (0 warnings, 0 errors).

Invoking Livetests

Copilot submitted PRs are not trustworthy by default. Users with write access 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.

tmeschter and others added 6 commits June 16, 2026 14:20
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 HttpOnBehalfOfTokenCredentialProvider by using a case-insensitive comparison.
  • Adds Microsoft.Mcp.Core.Helpers.StringComparisons to centralize intended StringComparison usage for common Azure identifiers.
  • Migrates multiple existing OrdinalIgnoreCase comparisons 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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are Azure resources always a case-insensitive comparison? This is the only one that gives me concern as it's a broad category.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

4 participants