Skip to content

Add FIC two-leg exchange over mTLS Proof-of-Possession (follow-up to #938)#939

Open
Robbie-Microsoft wants to merge 1 commit into
rginsburg/sni-mtls-popfrom
rginsburg/sni-mtls-pop-fic
Open

Add FIC two-leg exchange over mTLS Proof-of-Possession (follow-up to #938)#939
Robbie-Microsoft wants to merge 1 commit into
rginsburg/sni-mtls-popfrom
rginsburg/sni-mtls-pop-fic

Conversation

@Robbie-Microsoft

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #938 (SN/I certificate over mTLS Proof-of-Possession). Stacked on top of rginsburg/sni-mtls-pop — please review/merge #938 first; GitHub will auto-retarget this PR to dev once #938 merges.

Adds the optional two-leg Federated Identity Credential (FIC) exchange over mTLS PoP. The leg-2 confidential client presents the leg-1 (cert-bound mtls_pop) token as a jwt-pop client assertion over an mTLS connection bound by the same certificate, to obtain the final token.

Public API

# Leg 1: SN/I cert -> cert-bound mtls_pop token (see #938)
leg1 = leg1_app.acquire_token_for_client([exchange_scope], mtls_proof_of_possession=True)

# Leg 2: present the leg-1 token as a jwt-pop assertion over the same cert's mTLS connection
leg2_app = msal.ConfidentialClientApplication(
    client_id,
    authority="https://login.microsoftonline.com/<tenant-id>",
    client_credential={
        "client_assertion": leg1["access_token"],   # cert-bound leg-1 token
        "mtls_binding_certificate": {"private_key_pfx_path": "sni.pfx", "public_certificate": True},
    },
)
leg2 = leg2_app.acquire_token_for_client(scopes, mtls_proof_of_possession=True)

What changed (delta over #938)

Core

  • msal/oauth2cli/oauth2.py -- jwt-pop client-assertion-type constant.
  • msal/application.py -- detect the client_credential mtls_binding_certificate sub-key (FIC leg 2); carry the leg-1 assertion as a jwt-pop client assertion; route every FIC leg-2 request over the mTLS endpoint, so even a Bearer token travels the cert-bound connection (implicit Bearer-over-mTLS); error messages mention the FIC trigger.

Tests

  • tests/test_application.py -- FIC leg-2 (PoP and Bearer-over-mTLS), plus the string-assertion-without-binding-cert fail-fast.
  • tests/test_e2e.py -- E2E-3 FIC two-leg over mTLS PoP (self-skips when lab creds are absent).

Docs / samples

  • docs/index.rst -- FIC bullet in the mTLS PoP section.
  • Docstrings -- client_credential mtls_binding_certificate sub-key for FIC leg 2.
  • sample/confidential_client_mtls_pop_sample.py -- fic_two_leg_over_mtls_pop() example (gated by RUN_FIC_SAMPLE).

Verification: full unit suite 340 passed, 11 skipped on this branch (base + FIC); no regressions.

Stacked on top of the SN/I mTLS PoP base support. Enables a Federated
Identity Credential (FIC) two-leg exchange over mutual-TLS: a leg-1
cert-bound mtls_pop token is presented as a jwt-pop client assertion over
an mTLS connection bound by the same certificate to obtain the final token.

- Detect the client_credential mtls_binding_certificate sub-key (FIC leg 2)
  and carry the leg-1 assertion as a jwt-pop client assertion over mTLS
- Route every FIC leg-2 request over the mTLS endpoint, so even a Bearer
  token travels the cert-bound connection (implicit Bearer-over-mTLS)
- Add unit + E2E coverage, docs, and a two-leg sample

Co-authored-by: Copilot App <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

Adds support for the optional two-leg Federated Identity Credential (FIC) token exchange over mTLS Proof-of-Possession, where a leg-1 mtls_pop token is presented as a jwt-pop client assertion over an mTLS connection bound to the same certificate to obtain the final token.

Changes:

  • Introduces the jwt-pop client assertion type constant and uses it for FIC leg-2 requests.
  • Extends ConfidentialClientApplication to detect client_credential["mtls_binding_certificate"] (FIC leg 2) and route leg-2 traffic over the mTLS token endpoint (including implicit Bearer-over-mTLS).
  • Adds unit/E2E coverage, docs, and a gated sample for the two-leg flow.

Reviewed changes

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

Show a summary per file
File Description
msal/oauth2cli/oauth2.py Adds jwt-pop client assertion type constant for OAuth2 client assertions.
msal/application.py Implements FIC leg-2 detection and mTLS routing; sets client_assertion_type to jwt-pop for leg-2.
tests/test_application.py Adds unit tests validating jwt-pop assertion type and Bearer-over-mTLS behavior for FIC leg-2.
tests/test_e2e.py Adds an E2E test for the two-leg FIC exchange over mTLS PoP (self-skipping when not configured).
docs/index.rst Documents the FIC leg-2 mtls_binding_certificate configuration and jwt-pop behavior.
sample/confidential_client_mtls_pop_sample.py Adds a gated sample function demonstrating the two-leg FIC flow over mTLS PoP.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread msal/application.py
Comment on lines +862 to +865
if isinstance(client_credential, dict) and client_credential.get(
"mtls_binding_certificate"): # FIC leg 2 (assertion + binding cert)
self._mtls_cert_credential = client_credential["mtls_binding_certificate"]
self._mtls_is_fic_leg2 = True
Comment thread msal/application.py
Comment on lines 1034 to +1038
"mtls_proof_of_possession=True requires this confidential client "
"to be configured with a certificate credential (a "
"'private_key_pfx_path', or a 'private_key' plus "
"'public_certificate').")
"'public_certificate'); or, for a Federated Identity Credential "
"exchange, a 'client_assertion' plus an 'mtls_binding_certificate'.")
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.

2 participants