Add FIC Leg 2 over mTLS Proof-of-Possession (stacked on #1040) - #1041
Open
Robbie-Microsoft wants to merge 16 commits into
Open
Add FIC Leg 2 over mTLS Proof-of-Possession (stacked on #1040)#1041Robbie-Microsoft wants to merge 16 commits into
Robbie-Microsoft wants to merge 16 commits into
Conversation
Adds the 2-leg Federated Identity Credential (FIC) flow over mTLS PoP: an assertion-authenticated confidential client can bind to a certificate via ConfidentialClientApplication.Builder.mtlsBindingCertificate(...), presenting the binding certificate on the TLS handshake and authenticating the federated assertion with client_assertion_type=...:jwt-pop. Both legs are cert-bound and the final token is bound to the Leg-1 certificate thumbprint. Stacked on #1040 (core direct SN/I -> mTLS PoP); retarget base to dev after #1040 merges. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends MSAL4J’s confidential-client mTLS Proof-of-Possession (PoP) support to cover 2-leg Federated Identity Credential (FIC) “Leg 2” scenarios, where the app authenticates using a client assertion while still binding the resulting token to a certificate presented on the mTLS handshake.
Changes:
- Add
ConfidentialClientApplication.Builder.mtlsBindingCertificate(IClientCertificate)to supply a binding certificate for assertion-authenticated mTLS PoP requests. - Update token request construction to use
client_assertion_type=...:jwt-popwhenmtlsProofOfPossession()is set with a client assertion credential. - Re-introduce unit/integration tests and expand the sample/docs to demonstrate the 2-leg FIC over mTLS PoP flow.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ConfidentialClientApplication.java | Adds builder option + getter for mtlsBindingCertificate used to present a cert on the mTLS handshake for assertion-authenticated apps. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java | Switches client-assertion requests to jwt-pop assertion type under mTLS PoP and threads PoP context to assertion construction. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/MtlsClientCertificateHelper.java | Extends binding-certificate resolution to fall back to mtlsBindingCertificate for assertion-authenticated scenarios. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientCredentialParameters.java | Updates mTLS PoP Javadoc to mention mtlsBindingCertificate(...) for assertion-authenticated apps. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientAssertion.java | Introduces ASSERTION_TYPE_JWT_POP constant for client_assertion_type. |
| msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AssertionRequestOptions.java | Adds a PoP flag to the context passed to context-aware assertion providers. |
| msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MtlsProofOfPossessionTest.java | Re-adds unit coverage for “FIC Leg 2” behavior (jwt-pop + binding cert). |
| msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MtlsClientCertificateHelperTest.java | Adds unit test to ensure binding cert resolves correctly for assertion-authenticated apps. |
| msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/MtlsPopIT.java | Re-adds CI-only E2E test covering the 2-leg FIC mTLS PoP flow. |
| msal4j-sdk/src/samples/confidential-client/ClientCredentialMtlsProofOfPossession.java | Adds an end-to-end sample scenario showing developer-orchestrated 2-leg FIC over mTLS PoP. |
| changelog.txt | Notes the new 2-leg FIC over mTLS PoP capability. |
| .github/copilot-instructions.md | Updates internal repo guidance to mention mtlsBindingCertificate(...) for 2-leg FIC over mTLS PoP. |
The Unit Tests build's Credential Scanner (Guardian) gate flags the checked-in PKCS12 test cert (mtls_test_cert.p12), failing the build. It is a self-signed, test-only certificate (CN=msal4j-mtls-test) with no production secret, so allowlist it in build/credscan-exclude.json. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pulls in the mTLS PoP socket-factory JDK 8 fix and the ClientCredentialParameters thread-safety/comment fixes from #1040. Resolved copilot-instructions.md to keep the FIC Leg 2 sentence alongside the Key Classes markdown split. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The FIC Leg 2 mTLS PoP test now parses the url-encoded body and asserts exact decoded values for client_assertion, client_assertion_type (via the JWT_POP_ASSERTION_TYPE constant, previously unused), and token_type, instead of brittle substring checks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…-leg2 # Conflicts: # msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/MtlsClientCertificateHelper.java
…-leg2 # Conflicts: # msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/MtlsPopIT.java
Pulls in the fail-closed mTLS PoP token_type validation. Resolves conflicts keeping both features: the FIC Leg 2 tests and the parent's downgrade handling. Updates the ficLeg2 unit test mock to return token_type=mtls_pop (now that the response type is validated), and routes both FIC e2e legs through acquireMtlsPopOrSkipOnDowngrade so a slice downgrade on either leg is treated as inconclusive rather than a hard failure. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
resolveBindingCertificate only falls back to the app's mtlsBindingCertificate when the authenticating credential is an IClientAssertion (FIC Leg 2). A non-assertion credential (e.g. client secret) with a stray binding cert now fails with MTLS_POP_ERROR instead of silently binding. Also makes the ConfidentialClientApplication.mtlsBindingCertificate field private (only read via its getter). Adds a secret+bindingCert negative test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Robbie-Microsoft
added a commit
that referenced
this pull request
Jul 30, 2026
…name the X509 matrix Ports the rigor from the merged MSAL.NET reference (PR #6100) to the SNI mTLS PoP integration tests and docs: - Add reusable MtlsResourceCaller.callResourceWithMtlsToken(url, token, cert): presents the binding certificate on the TLS handshake (reusing createMtlsSocketFactory), sends Authorization: mtls_pop <token>, GETs the resource, returns the status; drains and never logs the external body. The headline PoP test now calls mtlstb.graph.microsoft.com and requires HTTP 200, proving the bound token is genuinely usable (not just well-formed). - Fix the @BeforeAll escape hatch: assertNotNull on the lab cert hard-failed the whole class off-CI; use Assumptions.assumeTrue so it skips instead (missing lab cert is an environment condition, not a defect). - Establish the Credential_X509_Output_<Pop|Bearer> naming: rename the PoP tests into the family and add a standalone Credential_X509_Output_Bearer (asserts BEARER + null binding cert) instead of relying only on the cache-isolation side effect. - Remove a redundant assertNotNull(thumbprint) immediately before assertEquals. - Add docs/mtls-pop.md documenting the correct public API (mtlsProofOfPossession(), result.metadata().tokenType()/bindingCertificate(), region optional/global fallback, tenanted authority, Bearer omission is mTLS-specific). Written fresh; not copied from the stale #1021 branch. - Fix the sample's missing ClientCredentialFactory import. The Fic E2E resource call reuses MtlsResourceCaller in the FIC follow-up (#1041). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bring the parent SNI branch (rginsburg/sni-mtls-pop @ d22059a) into the FIC branch with --no-ff. Resolve the shared MtlsPopIT.java conflict by adopting the SNI hardening for the direct-SNI cells while preserving the FIC two-leg test and its skip-on-downgrade hatch untouched: - Adopt: @BeforeAll Assumptions.assumeTrue env-absence skip; the Credential_X509_Output_{Pop,Bearer,Pop_CacheHit,Pop_And_Bearer_CacheIsolated} matrix; the MtlsResourceCaller resource-call assertEquals(200) on the headline Pop cell; Bearer on the regional endpoint; direct mtls_pop assertions (no skip). - Preserve: acquireTokenFic_TwoLeg_MtlsPop_BothLegsBound and the retained acquireMtlsPopOrSkipOnDowngrade helper it depends on (de-hatching FIC is Task 2). New parent-only files (docs/mtls-pop.md, MtlsResourceCaller.java) and the sample import merged cleanly. Integration-test sources compile. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bring the base-resynced parent (48fd92d: SN/I mTLS PoP + upstream dev through 56b9c6b) into the FIC Leg 2 branch. ClientCredentialParameters and TokenRequestExecutor auto-merged: dev's ExtendedCacheKey refactor / clientClaims support and this branch's FIC jwt-pop assertion path coexist. Only changelog.txt conflicted; resolved by keeping the 2-leg FIC entry under the unreleased 1.25.1 section alongside the SN/I mTLS PoP and dev entries. MtlsPopIT.java took this branch's version unchanged, so the FIC test (acquireTokenFic_TwoLeg_MtlsPop_ BothLegsBound) and its acquireMtlsPopOrSkipOnDowngrade hatch are preserved intact (de-hatch is Task 2). mvn -pl msal4j-sdk test = 405 green; integrationtest compiles. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…anch Brings in MtlsResourceCaller error-body surfacing + Credential_X509_Output_Pop assertion message. FIC test acquireTokenFic_TwoLeg_MtlsPop_BothLegsBound and its skip gate are untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pulls in MtlsResourceCaller Accept: application/json fix. FIC test acquireTokenFic_TwoLeg_MtlsPop_BothLegsBound and its acquireMtlsPopOrSkipOnDowngrade hatch untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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
Follow-up to #1040. Adds 2-leg Federated Identity Credential (FIC) over mTLS Proof-of-Possession (PoP): an app that authenticates with a federated client assertion can still bind the resulting token to a certificate. The assertion is sent with
client_assertion_type=...:jwt-popand the binding certificate is presented on the mTLS handshake, so the final token is bound to the Leg-1 certificate thumbprint.Public API
ConfidentialClientApplication.Builder.mtlsBindingCertificate(IClientCertificate)Internal changes
TokenRequestExecutor): under mTLS PoP with an assertion credential, the assertion is sent withclient_assertion_type=...:jwt-popand the binding cert is presented on the mutual-TLS handshake.MtlsClientCertificateHelper.resolveBindingCertificatefalls back to the configuredmtlsBindingCertificate(...)only when the credential is anIClientAssertion(FIC Leg 2); a non-assertion credential (e.g. a client secret) with a stray binding cert fails withMTLS_POP_ERROR.AssertionRequestOptionscarries the PoP flag through to assertion construction; adds theClientAssertion.ASSERTION_TYPE_JWT_POPconstant.Testing
ficLeg2_assertionWithBindingCert_usesJwtPopAssertionType(MtlsProofOfPossessionTest);resolveBindingCertificatepositive (assertion+cert) and negative (secret+cert →MTLS_POP_ERROR) tests (MtlsClientCertificateHelperTest). Suite green (385 tests).acquireTokenFic_TwoLeg_MtlsPop_BothLegsBound(MtlsPopIT, CI-only — needs the lab SN/I cert + an ESTS allow-listed resource).Out of scope
Unchanged from #1040: user-scoped (
user_fic) FIC over mTLS PoP, managed-identity first-leg mTLS, and the two legacy sovereign hosts.