Delegate IMDSv2 mTLS-PoP token leg to internal TokenClient exchange (MSIv2 WithClaimsFromClient)#6070
Merged
Merged
Conversation
Route IMDSv2's post-mint mTLS-PoP token leg through MSAL's internal TokenClient exchange (the path CCA uses) instead of a bespoke MI token POST. This builds on #5999 (WithClaimsFromClient) so MSIv2 client-originated claims ride the shared exchange and inherit the CP1 client-capability merge, claims-based cache keying, and ESTS error handling. - ManagedIdentityAuthRequest: unconditional delegation branch for mTLS PoP; mint binding cert, apply mtls_pop scheme, delegate to TokenClient; re-mint + retry once on invalid_client/SCHANNEL; normalize cached scope. - ManagedIdentityClient: AcquireImdsV2MtlsBindingAsync routed through shared source selection so the IMDSv1-not-supported guard still fires. - ImdsV2ManagedIdentitySource: extract cert-mint into AcquireMtlsBindingAsync; add AcquireMtlsBindingForDelegationAsync (mint-only, optional re-mint); IsSchanelFailure made internal for reuse. - Tests: claims-forwarding, same/different-claims cache keying, and invalid_client re-mint-and-retry coverage. ESTS-shaped IMDSv2 token mock helper (relative expires_in). Refs #6042 #5999 #5982 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the IMDSv2 managed identity mTLS Proof-of-Possession flow so the post-mint “token leg” is delegated to MSAL’s internal TokenClient exchange path (the same exchange machinery used by confidential client flows). This enables IMDSv2 to automatically inherit shared behaviors like merged claims/capabilities, claims-based cache keying, and standard ESTS error handling, and adds/updates unit tests and mocks to validate the new behavior.
Changes:
- Delegate IMDSv2 mTLS-PoP token acquisition to
TokenClient, including a one-time re-mint-and-retry oninvalid_client/ SCHANNEL-related failures. - Introduce a mint-only IMDSv2 binding acquisition path for delegation (cert + endpoint + client_id) and route it through the shared MI source-selection logic.
- Add unit tests + mock updates to assert claims forwarding in the ESTS-R POST body, claims-based cache keying, and re-mint behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs | Adds IMDSv2 mTLS-PoP tests for WithClaimsFromClient, cache keying by claims, and invalid_client re-mint retry. |
| src/client/Microsoft.Identity.Lab.Api/Http/MockHelpers.cs | Updates IMDSv2 token-leg mocks to validate delegated ESTS-R request shape (claims in body) and response shape (expires_in relative). |
| src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs | Extracts cert-mint into a reusable binding method and adds a mint-only delegation entrypoint; exposes SCHANNEL detection for reuse. |
| src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs | Adds API to mint/reuse IMDSv2 binding for delegation via shared MI source selection. |
| src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs | Routes mTLS-PoP requests through delegated TokenClient exchange and normalizes scope for MI cache behavior. |
…code - ManagedIdentityClient.AcquireImdsV2MtlsBindingAsync: replace the unchecked cast to ImdsV2ManagedIdentitySource with a guarded cast that throws a clear MsalClientException (MtlsPopNotSupportedForEnvironment) when the resolved managed identity source is a non-IMDS, environment-detected source, instead of an opaque InvalidCastException. - ManagedIdentityAuthRequest: use MsalError.InvalidClient instead of the "invalid_client" string literal in the delegated re-mint retry filter. - Remove dead code now that the IMDSv2 mTLS-PoP token leg is fully delegated: the unreachable mtls_pop scheme block in the legacy MI path, the dead bespoke token-request build in ImdsV2ManagedIdentitySource.CreateRequestAsync (now an explicit InvalidOperationException guard), and the now-superseded AuthenticateAsync SCHANNEL-retry override (the delegated path performs the invalid_client/SCHANNEL re-mint-and-retry). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mment - Move the inline mTLS-PoP "not supported for environment" message into MsalErrorMessage.cs (MtlsPopNotSupportedForManagedIdentityEnvironmentMessage) to match the centralized mTLS-PoP error-message family. - Strengthen the IMDSv2 CreateRequestAsync comment to explicitly warn against restoring a bespoke token request, preserving the TokenClient delegation invariant (claims/capability merge and claims-based cache keying). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bgavrilMS
reviewed
Jun 16, 2026
Avery-Dunn
reviewed
Jun 22, 2026
Avery-Dunn
approved these changes
Jun 22, 2026
neha-bhargava
approved these changes
Jun 24, 2026
This was referenced Jun 24, 2026
Closed
Merged
This was referenced Jun 27, 2026
Open
This was referenced Jun 29, 2026
fix: Bump Microsoft.Identity.Client from 4.85.1 to 4.85.2
Halceyon/open-telemetry-trace-listener#204
Closed
Merged
Closed
Open
Open
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds on #5999 (
WithClaimsFromClient) by routing IMDSv2's post-mint mTLS-PoP token leg through MSAL's internalTokenClientexchange (the same path CCA uses) instead of the bespoke Managed Identity token POST. Productionizes the spike from #6042 / #6062.With delegation, MSIv2 client-originated claims now ride the shared exchange and automatically inherit:
ClaimsAndClientCapabilities),— rather than re-implementing them in a bespoke MI token request. No
TokenClientchange required.Why delegation is the claims fix
AuthenticationRequestParameters.ClaimsAndClientCapabilitiesalready mergesClaims+ClientClaims+ client capabilities, andTokenClientalready sends that merged value as theclaimsbody param. Delegating the MSIv2 token leg toTokenClienttherefore forwards correctly-merged claims with no extra wiring.Changes
ManagedIdentityAuthRequest: unconditional delegation branch for mTLS PoP — mint binding cert, applymtls_popscheme, delegate toTokenClient; re-mint + retry once oninvalid_client/ SCHANNEL; normalize cached scope to the MI request scope.ManagedIdentityClient:AcquireImdsV2MtlsBindingAsyncrouted through shared source selection so the IMDSv1-not-supported guard (MtlsPopTokenNotSupportedinImdsV1) still fires. The cast to the IMDSv2 source is guarded — a non-IMDS, environment-detected source (App Service, Service Fabric, Cloud Shell, Azure Arc, Machine Learning) requesting mTLS PoP throws a clearMsalClientException(MtlsPopNotSupportedForEnvironment) rather than anInvalidCastException.ImdsV2ManagedIdentitySource: extract cert-mint intoAcquireMtlsBindingAsync; addAcquireMtlsBindingForDelegationAsync(mint-only, optional forced re-mint);IsSchanelFailuremadeinternalfor reuse. Removed the now-dead bespoke token-request build inCreateRequestAsync(now an explicitInvalidOperationExceptionguard, since the base method isabstract) and the supersededAuthenticateAsyncSCHANNEL-retry override.claimsasserted in ESTS-R body), same/different-claims cache keying, andinvalid_clientre-mint-and-retry coverage. New ESTS-shaped IMDSv2 token mock helper with relativeexpires_in.Note on SCHANNEL retry scope (conscious decision)
In the delegated design the cert-mint happens in
AcquireMtlsBindingForDelegationAsyncand the token leg carries theinvalid_client/ SCHANNEL re-mint-and-retry (withforceRemint:true→RemoveBadCert→ full CSR + issuecredential + token). This is intentional: SCHANNEL handshake failures arise from the client certificate presented in the mTLS handshake, which only occurs on the token POST to ESTS-R (whereMtlsCertificateis attached);/getplatformmetadataand/issuecredentialare plain HTTPS calls. A transiently-bad cert is therefore still fully re-minted on retry.Testing
ManagedIdentityTestsnamespace: 427 passed, 1 skipped.TreatWarningsAsErrors=true.PublicAPI.*.txtchanges —WithClaimsFromClientwas already shipped public API in WithClientClaims() - forward client-originated claims across MSI and confidential client flows #5999; no new public surface added.Refs #6042 #5999 #5982