Skip to content

Delegate IMDSv2 mTLS-PoP token leg to internal TokenClient exchange (MSIv2 WithClaimsFromClient)#6070

Merged
Robbie-Microsoft merged 4 commits into
mainfrom
rginsburg/imdsv2-token-delegation
Jun 24, 2026
Merged

Delegate IMDSv2 mTLS-PoP token leg to internal TokenClient exchange (MSIv2 WithClaimsFromClient)#6070
Robbie-Microsoft merged 4 commits into
mainfrom
rginsburg/imdsv2-token-delegation

Conversation

@Robbie-Microsoft

@Robbie-Microsoft Robbie-Microsoft commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Builds on #5999 (WithClaimsFromClient) by routing IMDSv2's post-mint mTLS-PoP token leg through MSAL's internal TokenClient exchange (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:

  • the CP1 client-capability merge (ClaimsAndClientCapabilities),
  • claims-based cache keying, and
  • canonical ESTS error handling

— rather than re-implementing them in a bespoke MI token request. No TokenClient change required.

Why delegation is the claims fix

AuthenticationRequestParameters.ClaimsAndClientCapabilities already merges Claims + ClientClaims + client capabilities, and TokenClient already sends that merged value as the claims body param. Delegating the MSIv2 token leg to TokenClient therefore forwards correctly-merged claims with no extra wiring.

Changes

  • 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 to the MI request scope.
  • ManagedIdentityClient: AcquireImdsV2MtlsBindingAsync routed 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 clear MsalClientException (MtlsPopNotSupportedForEnvironment) rather than an InvalidCastException.
  • ImdsV2ManagedIdentitySource: extract cert-mint into AcquireMtlsBindingAsync; add AcquireMtlsBindingForDelegationAsync (mint-only, optional forced re-mint); IsSchanelFailure made internal for reuse. Removed the now-dead bespoke token-request build in CreateRequestAsync (now an explicit InvalidOperationException guard, since the base method is abstract) and the superseded AuthenticateAsync SCHANNEL-retry override.
  • Tests: claims-forwarding (merged claims asserted in ESTS-R body), same/different-claims cache keying, and invalid_client re-mint-and-retry coverage. New ESTS-shaped IMDSv2 token mock helper with relative expires_in.

Note on SCHANNEL retry scope (conscious decision)

In the delegated design the cert-mint happens in AcquireMtlsBindingForDelegationAsync and the token leg carries the invalid_client / SCHANNEL re-mint-and-retry (with forceRemint:trueRemoveBadCert → 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 (where MtlsCertificate is attached); /getplatformmetadata and /issuecredential are plain HTTPS calls. A transiently-bad cert is therefore still fully re-minted on retry.

Testing

Refs #6042 #5999 #5982

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>

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 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 on invalid_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.

Robbie-Microsoft and others added 2 commits June 16, 2026 16:25
…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>
Copilot AI review requested due to automatic review settings June 16, 2026 20:33

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

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comment thread src/client/Microsoft.Identity.Lab.Api/Http/MockHelpers.cs
@Robbie-Microsoft Robbie-Microsoft enabled auto-merge (squash) June 23, 2026 14:21
This was referenced Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants