Skip to content

Support custom certificate-aware mTLS HTTP pipelines - #974

Draft
Robbie-Microsoft wants to merge 19 commits into
rginsburg/sni-mtls-popfrom
robbie-microsoft-mtls-http-extensibility
Draft

Robbie-Microsoft wants to merge 19 commits into
rginsburg/sni-mtls-popfrom
robbie-microsoft-mtls-http-extensibility

Conversation

@Robbie-Microsoft

@Robbie-Microsoft Robbie-Microsoft commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Scope

Follow-up to #938. Adds opt-in custom HTTP-pipeline support for the mTLS token endpoint. The default certificate-bearing transport and ordinary non-mTLS custom HTTP behavior remain unchanged.

Developer experience

A custom http_client opts in by adding create_mtls_client(binding_context) alongside its existing ordinary get/post methods. The argument is the public msal.MtlsBindingContext, not a token result. There is no marker class or new application constructor parameter.

def create_mtls_client(self, binding_context):
    session = self.new_policy_session()  # Fresh session with existing middleware/policy
    session.mount("https://", binding_context.create_requests_adapter(
        max_retries=self.retries,
        pool_connections=self.pool_connections,
        pool_maxsize=self.pool_maxsize,
        pool_block=True,
    ))
    self.mtls_sessions.append(session)  # Factory owns retention and cleanup
    return session

MtlsBindingContext.create_requests_adapter(**HTTPAdapter_options) reuses MSAL's certificate-bearing Requests transport, CA selection, proxy handling, and Requests-version compatibility. Other HTTP implementations can use create_ssl_context(verify=...) after resolving their effective verification policy.

sample/confidential_client_mtls_http_client_sample.py is a runnable example retaining middleware, CA/proxy/environment settings, timeout, retry, and pooling policy. It owns and closes both ordinary and derived pipelines.

Isolation and ownership

  • MSAL lazily creates and reuses one derived token pipeline per application/certificate. Creation is serialized within an application; different applications receive separate factory calls.
  • Factories must return a fresh client with callable post(url, **kwargs), isolated connection pools and TLS sessions, and the required networking/middleware policy. The factory owns cleanup, including partial constructions; MSAL does not close caller-owned clients.
  • Missing capabilities, malformed results, returning the ordinary client, and directly reusing another live application's client fail explicitly. Factory/network errors propagate without fallback.
  • Default and custom mTLS token paths enforce request-override and redirect protections before cache lookup. Ordinary HTTP behavior is unchanged.
  • This is a trusted implementer contract: MSAL cannot prove that arbitrary custom middleware uses the certificate or preserves networking policy.
  • binding_certificate is returned only for a final mtls_pop result, never for ordinary Bearer. Downgraded responses remain rejected and uncached.

Validation

Unit and integration coverage includes factory validation, default/ordinary compatibility, concurrent construction, client reuse, lifecycle ownership, redirects, request overrides, and token/HTTP cache isolation.

Loopback TLS tests verify the selected peer certificate, ordinary/discovery traffic without a client certificate, middleware and timeout policy, CA/proxy settings, isolated connection pools, certificate rotation, and failure without fallback.

Allow custom HTTP pipelines to create isolated certificate-bearing token clients using MtlsBindingContext. Preserve networking policy, pooling and Requests TLS compatibility, enforce observable transport contracts, and document ownership and trust boundaries.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@Robbie-Microsoft
Robbie-Microsoft requested a review from a team as a code owner October 23, 2026 15:29
Comment thread sample/confidential_client_mtls_http_client_sample.py Outdated
Comment thread tests/test_mtls_http_client.py
Preserve custom certificate-scoped pipelines, integrate parent transport and cache fixes, and reconcile redirect test contracts.

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

🟡 Changes recommended

The endpoint guard raises an exception for otherwise compatible custom responses whose URL metadata is a URL object.

1 open issue
What changed in this PR

Adds certificate-aware custom HTTP pipelines to the existing mTLS token flow while preserving the default transport and ordinary HTTP behavior.

Changes:

  • Adds an opt-in client factory and a public Requests adapter helper.
  • Enforces pipeline isolation, request restrictions, and endpoint checks.
  • Documents ownership requirements and adds a runnable sample with unit and local TLS coverage.
File Description
tests/​test_mtls_http_client.py Tests custom factories, isolation, lifecycle, and local TLS behavior.
tests/​test_application.py Updates redirect tests and transport cleanup.
sample/​confidential_client_mtls_pop_sample.py Updates comments to reference the custom pipeline sample.
sample/​confidential_client_mtls_http_client_sample.py Demonstrates policy-preserving pipelines and explicit cleanup.
msal/​mtls.py Adds adapter customization, ownership tracking, and endpoint guards.
msal/​application.py Integrates lazy custom pipeline creation and request validation.
docs/​index.rst Documents factory usage, trust boundaries, and lifecycle responsibilities.

Review effort: Balanced

Comment thread msal/mtls.py Outdated

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.

🔵 Needs a closer look

Custom certificate-bearing authentication pipelines introduce security-sensitive isolation, redirect, and lifecycle contracts that warrant final human review.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Robbie-Microsoft and others added 4 commits October 23, 2026 15:09
Distinguish the inherited live matrix from custom HTTP factory loopback TLS coverage without claiming cross-SDK live factory parity.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Allow the mocked legacy release-skip attribute to be created when the E2E module was imported without TRAVIS_TAG.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Robbie-Microsoft and others added 6 commits October 27, 2026 10:43
Redistribute the approved factory recorder/privacy/lifecycle slice from combined source 0cd290d without importing enabled Bearer-over-mTLS or real FIC integration. Preserve sample middleware, factory ownership, safe diagnostics, and the explicit loopback TLS floor. Shared generic E2E fixtures are supplied by the dependent foundation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the extensibility branch inventory separate from combined Bearer/FIC scenarios and reuse the foundation certificate fixture for rendered traceback redaction.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

# Conflicts:
#	tests/test_e2e.py
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Assert the owned shared transport closes once without closing endpoint wrappers, while retaining the legacy-layout assertions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

# Conflicts:
#	tests/test_mtls_e2e_helpers.py
Robbie-Microsoft added a commit that referenced this pull request Oct 27, 2026
Inherit the factory recorder and privacy guards in their owner modules, keep combined-only Bearer/FIC transport cases and the full 21-method live inventory, and avoid duplicate core collection.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Robbie-Microsoft added a commit that referenced this pull request Oct 27, 2026
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Robbie-Microsoft and others added 3 commits October 27, 2026 11:58
Catch Requests errors at the hidden outer POST boundary so real downstream DNS frames cannot render posted assertions or refresh tokens. Cover ordinary and derived clients through the actual Requests/urllib3 path.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rethrow only the private recorder transport failure outside OAuth call frames. Exercise real application client-credential and OBO DNS failures with expanded pytest tracebacks. Keep cert=None cold/cache behavior covered without changing production code.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Python 3.9 renders the inline dummy assertion in the visible leaf source even after transport exceptions are sanitized. Keep only the acquisition call in a hidden helper; leave every validation assertion visible and preserve all full/prefix/suffix privacy checks. Reproduced before and verified after on Python 3.9.25 with pytest 8.4.2.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Robbie-Microsoft added a commit that referenced this pull request Oct 27, 2026
Preserve the equivalent hidden combined invalid-grant acquisition boundary and all strict existing validation; no combined tree change is required.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Robbie-Microsoft and others added 2 commits October 27, 2026 12:43
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use the shared private validation sentinel only around token_request_summary and rethrow it outside OAuth frames with a static validation diagnostic, distinct from transport errors. Cover real-app bad JSON and signed-assertion validation in client credentials and OBO without masking unrelated assertions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

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.

I do not see a support for non-exportable CNGs. This is the key scenario.

@Robbie-Microsoft
Robbie-Microsoft marked this pull request as draft October 29, 2026 20:04

This branch has not been deployed

No deployments
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