Skip to content

Add MSI mTLS PoP support: pure MI + FIC-with-MI (impl for devex #3832)#3839

Draft
gladjohn wants to merge 1 commit into
masterfrom
gladjohn/msi-fic-mtls-pop-fresh
Draft

Add MSI mTLS PoP support: pure MI + FIC-with-MI (impl for devex #3832)#3839
gladjohn wants to merge 1 commit into
masterfrom
gladjohn/msi-fic-mtls-pop-fresh

Conversation

@gladjohn
Copy link
Copy Markdown
Contributor

@gladjohn gladjohn commented Jun 2, 2026

Summary

Implements the devex proposal in #3832: mTLS Proof-of-Possession for Managed Identity (pure MSI) and FIC backed by Managed Identity (federated identity credentials).

Triggered by setting AuthorizationHeaderProviderOptions.ProtocolScheme = `MTLS_POP`` on the downstream API options (together with RequestAppToken = true). No new public API.

Devex (the entire dev-facing surface)

json "AzureKeyVault": { "RequestAppToken": true, "ProtocolScheme": "MTLS_POP", "AcquireTokenOptions": { "ManagedIdentity": { "UserAssignedClientId": "..." } } }

For FIC-with-MI, add the SignedAssertionFromManagedIdentity credential under AzureAd.ClientCredentials — the binding cert minted by MI flows through to the outer CCA automatically.

What changed (~165 LOC of real code)

File Change
TokenAcquisition.cs Chain WithMtlsProofOfPossession().WithAttestationSupport() on the pure-MI builder when IsTokenBinding is set
ConfidentialClientApplicationBuilderExtension.cs Dispatch FIC-with-MI to a bound-assertion delegate returning ClientSignedAssertion (carrying the binding cert). All other signed-assertion source types still throw IDW10115
ManagedIdentityClientAssertion.cs New internal GetSignedAssertionWithBindingAsync — stateless, no shared mutable flag
Microsoft.Identity.Web.Certificateless.csproj Reference Microsoft.Identity.Client.KeyAttestation 4.84.1-preview
Properties/InternalsVisibleTo.cs IVT for Microsoft.Identity.Web.TokenAcquisition (3rd entry, consistent with existing precedent)

Why internal + IVT (not public)

The new method's return type is MSAL's ClientSignedAssertion — exposing it would tie ID Web's public surface to MSAL's API shape forever. Keeping it internal honors the devex spec's "no new public API" goal and avoids two methods on ManagedIdentityClientAssertion that consumers might confuse.

Why not the previous design (mutable IsTokenBinding property)

The prior approach (#3773) added a public IsTokenBinding { get; set; } flag on ManagedIdentityClientAssertion that callers had to set before invoking GetSignedAssertionAsync. That has three issues:

  1. Mutating a DI singleton from request-scoped code = thread-safety race
  2. Permanent public API for purely internal plumbing
  3. The base GetSignedAssertionAsync returns string, so the binding cert was effectively dropped

This PR replaces it with a stateless delegate that returns ClientSignedAssertion (assertion + cert pair), so the binding cert flows correctly into MSAL.

Tests

  • 2 new unit tests in WithClientCredentialsTests.cs:
    • WithBindingCertificateAsync_FicWithManagedIdentityAssertion_ReturnsBuilder — dispatch succeeds
    • WithBindingCertificateAsync_FicWithFileBasedAssertion_StillThrows — IDW10115 regression guard
  • Full suite (net8.0): 930 passed, 11 skipped, 0 failed
  • Pre-existing isTokenBinding cert-based tests unchanged and still pass

Samples

Two new daemon samples under tests/DevApps/daemon-app/:

  • daemon-app-msi-mtls — pure MI mTLS PoP
  • daemon-app-fic-mtls — FIC-with-MI mTLS PoP

Builds

  • Microsoft.Identity.Web.TokenAcquisition (all 6 TFMs): 0 warn / 0 err
  • Both samples: 0 warn / 0 err

Linked

Closes #3832 (devex)


Implements devex spec #3832:

- TokenAcquisition: chain WithMtlsProofOfPossession().WithAttestationSupport()
  on the pure-MI builder when ProtocolScheme=MTLS_POP.
- ConfidentialClientApplicationBuilderExtension: dispatch FIC-with-MI to a
  bound-assertion delegate that returns ClientSignedAssertion (carrying both
  the JWT and the MI-minted binding certificate). All other signed-assertion
  source types still throw IDW10115 (preserved by regression test).
- ManagedIdentityClientAssertion: new internal GetSignedAssertionWithBindingAsync
  that calls AcquireTokenForManagedIdentity(...).WithMtlsProofOfPossession()
  .WithAttestationSupport() and returns the bound assertion + cert pair.
- Reference Microsoft.Identity.Client.KeyAttestation 4.84.1-preview.
- IVT from Certificateless to TokenAcquisition (3rd entry in established file).
- 2 new unit tests in WithClientCredentialsTests.cs (930 total pass, 0 fail).
- 2 new daemon samples: daemon-app-msi-mtls, daemon-app-fic-mtls.

No new public API surface.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant