Repository navigation
Support custom certificate-aware mTLS HTTP pipelines - #974
Open
Robbie-Microsoft wants to merge 7 commits into
Open
Robbie-Microsoft wants to merge 7 commits into
Robbie-Microsoft wants to merge 7 commits into
Conversation
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
added this pull request to stack #975
October 23, 2026 15:31
| os.environ["SCOPE"].split(), mtls_proof_of_possession=True) | ||
| print("Acquired mTLS-PoP token:", result.get("token_type") == "mtls_pop") | ||
| if "error" in result: | ||
| print("Error code:", result["error"]) |
| def _start_server(self, handler, context=None): | ||
| server = ThreadingHTTPServer(("127.0.0.1", 0), handler) | ||
| if context is not None: | ||
| server.socket = context.wrap_socket(server.socket, server_side=True) |
Robbie-Microsoft
removed this pull request from stack #975
October 23, 2026 15:41
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>
Contributor
There was a problem hiding this comment.
🟡 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
Contributor
There was a problem hiding this comment.
🔵 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
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>
This branch has not been deployed
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.

Dependency and scope
Depends on #938. This follow-up targets
rginsburg/sni-mtls-pop, starting at9e35a334cf79cf493409bfa5db53da2b9245690c, rather thandevor either sibling feature branch. It changes mTLS token-endpoint HTTP extensibility, not FIC, Bearer-over-mTLS, managed identity, non-exportable keys, or downstream resource authentication.Developer experience
The default path still handles certificate-bearing TLS automatically, with no additional configuration. Ordinary non-mTLS custom HTTP clients and per-request
postoverrides retain their existing behavior.A custom
http_clientexplicitly opts in by addingcreate_mtls_client(binding_context)alongside its existing ordinaryget/postmethods. The argument is the existing publicmsal.MtlsBindingContext, not a token result. There is no marker class or new application constructor parameter.For example, inside the custom client's factory:
The new
MtlsBindingContext.create_requests_adapter(**HTTPAdapter_options)reuses MSAL's certificate-bearing Requests transport, effective CA selection, proxy handling, and Requests-version compatibility fixes. Other HTTP implementations can use the existingcreate_ssl_context(verify=...)helper after resolving their effective verification policy.sample/confidential_client_mtls_http_client_sample.pyis a runnable developer example retaining header policy, response middleware, CA/proxy/environment settings, timeout, retry and pooling configuration. It explicitly owns and closes ordinary and derived pipelines. The existing default mTLS sample now points to it.Isolation, ownership and trust contract
post(url, **kwargs), isolated connection pools and TLS sessions, and the same required networking/middleware policy. The factory owns cleanup, including its own partial constructions; MSAL does not close caller-owned clients.post/certoverrides and redirect opt-ins before cache lookup. Token transport calls are confined to the configured HTTPS token endpoint withallow_redirects=False; redirect responses, observable redirect history and final-endpoint changes are rejected before caching. Ordinary HTTP behavior is unchanged.binding_certificateis still returned only for a finalmtls_popresult, never for ordinary Bearer. Downgraded responses remain rejected and uncached.Validation
278 tests passed, with 61 subtests passed, on Windows / Python 3.14.7, Requests 2.34.2, urllib3 2.7.0 and OpenSSL 3.5.7:
Coverage includes malformed factories and propagated errors; legacy/default/ordinary compatibility; concurrent construction; direct-client reuse rejection and application garbage collection; final-token contracts; redirects and request overrides; token/HTTP cache isolation; and factory-owned lifecycle behavior.
Real local TLS evidence: the runnable custom pipeline presents the exact selected peer certificate while ordinary/discovery traffic presents none. Loopback tests retain middleware and timeout policy, verify session/request/environment CA settings and isolated verification pools, reuse connections, rotate between two certificates without shared pools/cache pollution, and traverse an actual local HTTPS CONNECT proxy with the selected certificate. Untrusted-server failures do not fall back to the ordinary client. Existing Requests compatibility regressions also run.
The API documentation passes a clean Sphinx rebuild with warnings treated as errors. Live Entra E2E was not run; local TLS validation uses generated test certificates and requires no lab credentials. No PR is merged by this change.