Skip to content

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

Merged
Gladwin Johnson VR (gladjohn) merged 4 commits into
masterfrom
gladjohn/msi-fic-mtls-pop-fresh
Jun 10, 2026
Merged

Gladwin Johnson VR (gladjohn) merged 4 commits into
masterfrom
gladjohn/msi-fic-mtls-pop-fresh

Conversation

@gladjohn

@gladjohn Gladwin Johnson VR (gladjohn) commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

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 with
MI as the assertion source).

Adds a small public extension point on the existing public ClientAssertionProviderBase
so any future binding-capable assertion provider (e.g. OIDC FIC, tracked separately in
#3851) can opt in without further plumbing changes in Microsoft.Identity.Web.TokenAcquisition.

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

Devex (the entire dev-facing surface)

"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

Source

File Change
ClientAssertionProviderBase.cs Two new public virtual extension points: SupportsTokenBinding (default false) and GetSignedAssertionWithBindingAsync (default returns null).
ManagedIdentityClientAssertion.cs Overrides both: SupportsTokenBinding => true, GetSignedAssertionWithBindingAsync returns a ClientSignedAssertion (assertion + binding cert pair). Bearer and bound paths share a private AcquireManagedIdentityTokenAsync helper.
ConfidentialClientApplicationBuilderExtension.cs Generic dispatch: checks credential.CachedValue is ClientAssertionProviderBase { SupportsTokenBinding: true } — no narrowing to a concrete type. Other signed-assertion sources still throw IDW10115.
TokenAcquisition.cs Chains WithMtlsProofOfPossession().WithAttestationSupport() on the pure-MI builder when IsTokenBinding is set.
IDWebErrorMessage.cs Reworded IDW10115 to reflect the new opt-in model.
PublicAPI/PublicAPI.Unshipped.txt +4 entries for the new public virtuals + MI overrides.
Microsoft.Identity.Web.Certificateless.csproj + Directory.Build.props Reference Microsoft.Identity.Client.KeyAttestation via a centrally-managed version variable.

No new internal API and no InternalsVisibleTo entry — the extension point lives on the
existing public base type.

Tests + samples

File Change
tests/Microsoft.Identity.Web.Test/Certificates/WithClientCredentialsTests.cs +91 lines of new unit coverage
tests/DevApps/daemon-app/daemon-app-msi-mtls/ New sample: pure MI mTLS PoP
tests/DevApps/daemon-app/daemon-app-fic-mtls/ New sample: FIC-with-MI mTLS PoP
Microsoft.Identity.Web.sln Wires up the two new sample projects

Design notes

The opt-in lives on the public abstract ClientAssertionProviderBase rather than on the
concrete ManagedIdentityClientAssertion. This:

Stateless by design: GetSignedAssertionWithBindingAsync returns the assertion + cert
pair fresh each call. No shared mutable flags, no DI-singleton thread-safety races.

Tests

  • New unit tests in WithClientCredentialsTests.cs:
    • WithBindingCertificateAsync_FicWithManagedIdentityAssertion_ReturnsBuilder — dispatch succeeds.
    • WithBindingCertificateAsync_FicWithFileBasedAssertion_StillThrows — IDW10115 regression guard.
  • Full suite: 3,003 passed, 19 skipped, 0 failed.
    (Microsoft.Identity.Web.Test net462/472/net8/net9/net10 + Microsoft.Identity.Web.UI.Test net8/net9.)

Builds

  • Microsoft.Identity.Web.Certificateless (all TFMs): 0 warn / 0 err.
  • Microsoft.Identity.Web.TokenAcquisition (all TFMs): 0 warn / 0 err.
  • Both samples: 0 warn / 0 err.
  • TreatWarningsAsErrors=true enforced.

Linked

@gladjohn
Gladwin Johnson VR (gladjohn) marked this pull request as ready for review June 8, 2026 15:25
@gladjohn
Gladwin Johnson VR (gladjohn) requested a review from a team as a code owner June 8, 2026 15:25
gladjohn and others added 2 commits June 8, 2026 08:53
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>
…o GA 4.84.2

- Rename CancellationToken variable per cpp11nullptr review feedback to
  avoid confusion with OAuth/auth tokens (effectiveToken ->
  effectiveCancellationToken in GetSignedAssertionWithBindingAsync).
- Bump Microsoft.Identity.Client.KeyAttestation from 4.84.1-preview to
  GA 4.84.2 so it aligns with MSAL 4.84.2 already on master.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gladjohn
Gladwin Johnson VR (gladjohn) force-pushed the gladjohn/msi-fic-mtls-pop-fresh branch from 83aae6b to 8a4db7f Compare June 8, 2026 15:57

@bgavrilMS Bogdan Gavril (bgavrilMS) left a comment

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.

LGTM, but please make sure you also update the OIDC FIC (i.e. FIC from other Identity Provider) code path.

@gladjohn

Copy link
Copy Markdown
Contributor Author

LGTM, but please make sure you also update the OIDC FIC (i.e. FIC from other Identity Provider) code path.

Thanks Bogdan Gavril (@bgavrilMS). Just to make sure we're aligned before I touch that path - by "OIDC FIC code path" do you mean the federated assertion coming from OidcIdpSignedAssertionProvider (i.e., Microsoft.Identity.Web.OidcFIC)?

From the code I see that OIDC IdP only hands us a JWT, not a binding certificate. For mTLS PoP on that path, are you expecting:

  • a clearer error when someone configures token binding with an OIDC-IdP credential (so it fails fast and explains why), or
  • wiring it up so the OIDC-IdP assertion can be paired with a separate binding-cert credential and the outer CCA actually issues a PoP token?

Just want to make sure I match what you have in mind.

@bgavrilMS

Copy link
Copy Markdown
Member

LGTM, but please make sure you also update the OIDC FIC (i.e. FIC from other Identity Provider) code path.

Thanks Bogdan Gavril (@bgavrilMS). Just to make sure we're aligned before I touch that path - by "OIDC FIC code path" do you mean the federated assertion coming from OidcIdpSignedAssertionProvider (i.e., Microsoft.Identity.Web.OidcFIC)?

From the code I see that OIDC IdP only hands us a JWT, not a binding certificate. For mTLS PoP on that path, are you expecting:

  • a clearer error when someone configures token binding with an OIDC-IdP credential (so it fails fast and explains why), or
  • wiring it up so the OIDC-IdP assertion can be paired with a separate binding-cert credential and the outer CCA actually issues a PoP token?

Just want to make sure I match what you have in mind.

Yes, Microsoft.Identity.Web.OidcFIC. It should be made to work with bound FIC + bearer or bound tokens

@gladjohn

Copy link
Copy Markdown
Contributor Author

LGTM, but please make sure you also update the OIDC FIC (i.e. FIC from other Identity Provider) code path.

Thanks Bogdan Gavril (@bgavrilMS). Just to make sure we're aligned before I touch that path - by "OIDC FIC code path" do you mean the federated assertion coming from OidcIdpSignedAssertionProvider (i.e., Microsoft.Identity.Web.OidcFIC)?
From the code I see that OIDC IdP only hands us a JWT, not a binding certificate. For mTLS PoP on that path, are you expecting:

  • a clearer error when someone configures token binding with an OIDC-IdP credential (so it fails fast and explains why), or
  • wiring it up so the OIDC-IdP assertion can be paired with a separate binding-cert credential and the outer CCA actually issues a PoP token?

Just want to make sure I match what you have in mind.

Yes, Microsoft.Identity.Web.OidcFIC. It should be made to work with bound FIC + bearer or bound tokens

Based on our conversation - tracking OIDC work in its own task - #3851

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.

3 participants