Simplify GetTenantedAuthority in CiamAuthority and DstsAuthority (follow-up to #5958)#6001
Conversation
There was a problem hiding this comment.
Pull request overview
This PR follows up on #5958 by continuing the “consumers/MSA GUID is a real tenant” fix across authority handling, token cache filtering, and regression tests, and by attempting to simplify tenant override behavior in CIAM/DSTS authorities.
Changes:
- Updates AAD logic to treat only
common/organizationsas tenantless (notconsumers/ MSA GUID) and aligns request-authority selection, cache filtering, and mTLS PoP validation accordingly. - Adds/updates unit tests to ensure request-level tenant overrides are honored for
consumers/ MSA GUID scenarios and that the correct token endpoint is used. - Refactors
GetTenantedAuthorityoverrides for CIAM and DSTS to return the canonical authority unchanged.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs | Adds regression coverage for request-level tenant override when app authority is MSA GUID; updates a consumers-related assertion. |
| tests/Microsoft.Identity.Test.Unit/CoreTests/InstanceTests/AadAuthorityTests.cs | Adds regression test ensuring MSA GUID is not treated as tenantless by GetTenantedAuthority. |
| tests/Microsoft.Identity.Test.Unit/ApiConfigTests/AuthorityTests.cs | Adds request-level authority override regression coverage for MSA GUID/consumers and introduces new static test authorities. |
| src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs | Updates tenant filtering to treat consumers/MSA GUID as tenanted (filterable). |
| src/client/Microsoft.Identity.Client/Instance/DstsAuthority.cs | Changes DSTS GetTenantedAuthority behavior to always return canonical authority. |
| src/client/Microsoft.Identity.Client/Instance/CiamAuthority.cs | Changes CIAM GetTenantedAuthority behavior to always return canonical authority and removes unused usings. |
| src/client/Microsoft.Identity.Client/Instance/AadAuthority.cs | Removes consumers from the “tenantless” set and drops the IsCommonOrganizationsOrConsumersTenant helper. |
| src/client/Microsoft.Identity.Client/AppConfig/AuthorityInfo.cs | Updates request-authority selection logic/comments to only treat common/organizations as tenantless. |
| src/client/Microsoft.Identity.Client/ApiConfig/Parameters/MtlsPopParametersInitializer.cs | Updates mTLS PoP validation to only reject common/organizations tenantless authorities. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/client/Microsoft.Identity.Client/Instance/DstsAuthority.cs:42
DstsAuthority.GetTenantedAuthoritynow rewrites the authority only whenforceSpecifiedTenant=true. This changes behavior for DSTS authorities configured with tenantless segments like/dstsv2/common/or/dstsv2/organizations/: calls that tenantize an authority withforceSpecifiedTenant:false(e.g.,Authority.CreateAuthorityWithTenant(...)used by token/caching helpers) will no longer replace the tenant and will keepcommon/organizations, which can lead to incorrect tenant IDs and cache partitioning. Consider restoring the non-forced rewrite when the currentTenantIdis tenantless (e.g.,common/organizations) while still keeping the forced rewrite for request-level.WithTenantId(...).
// DSTS authorities use their own URL template (dstsv2/{tenantId}/), not the AAD template.
// Only honor tenant overrides when forceSpecifiedTenant=true (i.e., .WithTenantId() at request level).
// The old non-forced path (IsCommonOrOrganizationsTenant) was dead because DSTS authorities
// always carry a real tenant, never "common" or "organizations".
internal override string GetTenantedAuthority(string tenantId, bool forceSpecifiedTenant = false)
{
if (!string.IsNullOrEmpty(tenantId) && forceSpecifiedTenant)
{
var authorityUri = AuthorityInfo.CanonicalAuthority;
return string.Format(
CultureInfo.InvariantCulture,
DstsCanonicalAuthorityTemplate,
authorityUri.Authority,
tenantId);
}
return AuthorityInfo.CanonicalAuthority.ToString();
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 8 comments.
Comments suppressed due to low confidence (3)
src/client/Microsoft.Identity.Client/Internal/ClaimsHelper.cs:96
SortJsonObjectKeysonly recurses intoJObjectchildren. Object keys nested inside JSON arrays (e.g.,{"id_token":{"acr":{"values":[{"z":1,"a":2}]}}}or any complex claim using arrays of objects) will not be sorted, so two semantically identical claims values that differ only in key ordering inside an array element will normalize to different strings and produce separate cache entries — defeating the stated goal of preventing cache key fragmentation. Consider recursing throughJsonArrayelements as well (sorting object elements while preserving array element order).
private static JObject SortJsonObjectKeys(JObject obj)
{
var sorted = new JObject();
foreach (var key in obj.Select(kvp => kvp.Key).OrderBy(k => k, StringComparer.Ordinal))
{
var value = obj[key];
if (value is JObject nestedObj)
{
sorted[key] = SortJsonObjectKeys(nestedObj);
}
else
{
// JsonNode.DeepClone is .NET 6+; use Parse(ToJsonString()) for portability.
sorted[key] = value is null ? null : JsonNode.Parse(value.ToJsonString());
}
}
return sorted;
}
src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs:72
- String literal
"claims"is used here for the query/body parameter name, butOAuth2Parameter.Claimsis the established constant for this name (and is used in the test on line 433). Please useOAuth2Parameter.Claimsinstead of the bare string literal in both branches to keep the wire-format key in one place and avoid drift.
request.QueryParameters["claims"] = Uri.EscapeDataString(parameters.ClientClaims);
_requestContext.Logger.Info("[Managed Identity] Adding client claims to IMDS request as query parameter.");
}
else
{
request.BodyParameters["claims"] = parameters.ClientClaims;
src/client/Microsoft.Identity.Client/Internal/ClaimsHelper.cs:76
- The
MergeClaimsObjectscatch block reusesMsalErrorMessage.InvalidJsonClaimsFormat(claims1)which embeds the rawclaims1value into the exception message. This contradicts the comment on the sister methodNormalizeClaimsJson(lines 44-45) which deliberately does not include the raw JSON because "it may contain sensitive data". Either both should sanitize, or the comment inNormalizeClaimsJsonis misleading. Please align — preferably do not embed the raw claims JSON in the merge error message either.
catch (Exception ex) when (ex is JsonException || ex is InvalidOperationException)
{
// InvalidOperationException is thrown by JsonNode.AsObject() when a value is
// valid JSON but not an object (e.g. an array or a scalar).
throw new MsalClientException(
MsalError.InvalidJsonClaimsFormat,
MsalErrorMessage.InvalidJsonClaimsFormat(claims1),
ex);
}
trwalke
left a comment
There was a problem hiding this comment.
Is this the correct description for this PR? the comment seems to suggest this is refactoring but the code looks like you are adding the withClientCliams api?
The title/description was stale. Updated them to cover both the new WithClientClaims(string) API and the GetTenantedAuthority cleanup. |
- Restore non-forced rewrite in DstsAuthority.GetTenantedAuthority so tenantless DSTS authorities (.../dstsv2/common/ or /organizations/) are still rewritten by CreateAuthorityWithTenant in the silent-flow / ADAL legacy cache path. Adds DstsAuthorityTests regression tests. - ClaimsHelper.SortJsonObjectKeys now recurses through JsonArray so objects nested inside arrays also have their keys sorted, preventing cache-key fragmentation. Adds three ClaimsHelperTests. - WithClientClaims XML docs (MI + ConfApp) now document the experimental-feature requirement and clarify the payload must be a valid JSON object. - Add two negative tests asserting WithClientClaims without WithExperimentalFeatures(true) throws MsalClientException. - AbstractManagedIdentity: drop redundant full qualification of HttpMethod.Get and add comment explaining intentional GET/POST encoding asymmetry. - ClaimsHelper.cs: move 'using System;' to top of using block. - WithClientClaimsTests.cs: remove spurious blank lines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… follow-up) Follow-up to #5958 addressing reviewer feedback (#5958 (comment)): - CiamAuthority: remove dead/unreachable code paths in GetTenantedAuthority. - DstsAuthority: keep the silent-flow rewrite path that Authority.CreateAuthorityWithTenant relies on (tenantless dstsv2/common / dstsv2/organizations configured authorities must be rewritten to the tenanted form even when forceSpecifiedTenant is false), and document the contract in a comment. Adds two regression tests covering the non-forced rewrite for both `common` and `organizations`. - AuthorityInfo: tighten an XML doc comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
065db72 to
7d7309b
Compare
|
@gladjohn — to your earlier question about why so many files were changing here: you were right, the diff had drifted well beyond the CIAM/DSTS cleanup you originally flagged. The branch had picked up the I've force-pushed the branch down to just the original scope — a single commit (
The Sorry for the noise — ready for another look when you have a moment. |
Simplifies the
GetTenantedAuthoritylogic inCiamAuthorityandDstsAuthority, following up on @gladjohn's feedback on #5958 (#5958 (comment)).Changes
CiamAuthority.GetTenantedAuthority— remove dead/unreachable code paths.DstsAuthority.GetTenantedAuthority— keep the silent-flow rewrite path thatAuthority.CreateAuthorityWithTenantrelies on (tenantlessdstsv2/common/dstsv2/organizationsconfigured authorities must be rewritten to the tenanted form even whenforceSpecifiedTenantis false), and document the contract with a comment. Adds two regression tests covering the non-forced rewrite for bothcommonandorganizations.AuthorityInfo— tighten an XML doc comment.Scope note
The earlier history of this branch had unintentionally bundled the
WithClientClaims(string)work via local merges. That work is owned by #5999 and has been removed from here via a force-push. The original bundled history is preserved at the tagpr6001-backup-pre-rebasefor reference.