Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f98961f
Add Bearer-over-mTLS: app-level flag + client-credentials path
Robbie-Microsoft Jul 31, 2026
93faa2f
Route OBO/refresh/auth-code through Bearer-over-mTLS when flag is set
Robbie-Microsoft Jul 31, 2026
bf79927
Add Bearer-over-mTLS second-call cache-hit regression
Robbie-Microsoft Jul 31, 2026
ab024e7
Add Bearer-over-mTLS end-to-end cells
Robbie-Microsoft Jul 31, 2026
92421b0
Document the send_certificate_over_mtls client_credential flag
Robbie-Microsoft Jul 31, 2026
60a9f24
Harden Bearer-over-mTLS 2nd-call cache test with an env-lock assertion
Robbie-Microsoft Jul 31, 2026
3b45837
Merge updated sni-mtls-pop foundation
Robbie-Microsoft Oct 9, 2026
6e2c371
Address mTLS review feedback
Robbie-Microsoft Oct 21, 2026
3c66b97
Merge updated sni-mtls-pop foundation
Robbie-Microsoft Oct 21, 2026
a3ee807
Fix Bearer-over-mTLS transport enforcement and PFX chains
Robbie-Microsoft Oct 22, 2026
c467604
Merge mTLS binding context foundation
Robbie-Microsoft Oct 22, 2026
5818a5c
Merge mTLS PoP foundation and align Bearer semantics
Robbie-Microsoft Oct 23, 2026
8d4ecec
Merge PR #938 into Bearer-over-mTLS follow-up
Robbie-Microsoft Oct 23, 2026
30281ce
Fix Bearer-over-mTLS routing, certificate overrides and PFX compatibi…
Robbie-Microsoft Oct 23, 2026
820c96f
Align Bearer-over-mTLS E2E failure and coverage semantics
Robbie-Microsoft Oct 23, 2026
b241b3d
Merge shared fail-closed certificate E2E fixtures
Robbie-Microsoft Oct 23, 2026
063325c
Merge shared E2E fixture portability fix
Robbie-Microsoft Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ Notes and requirements:
``os.PathLike`` objects (including ``pathlib.Path``); these replace the default
trust roots. ``True`` or ``None`` selects the Requests CA bundle, while
``False`` disables server verification.
* To request Bearer-over-mTLS instead, set
``send_certificate_over_mtls=True`` in the certificate credential and omit
``mtls_proof_of_possession``. The resulting token uses the ``Bearer`` scheme,
is not certificate-bound, and does not include ``binding_certificate``.

mTLS-bound access tokens are persisted in a separate ``AccessToken_mTLS`` cache
bucket. Older SDK versions ignore that bucket and retain their normal Bearer
Expand Down
185 changes: 173 additions & 12 deletions msal/application.py

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions msal/mtls.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ def get(self, url, **kwargs):
def _request(self, method, url, **kwargs):
from requests.exceptions import HTTPError

if "cert" in kwargs:
raise ValueError(
"MSAL's mTLS transport does not support a cert override. "
"Use the configured client certificate.")
if self._timeout is not None:
kwargs.setdefault("timeout", self._timeout)
# The certificate and token request must stay at the selected authority.
Expand Down
54 changes: 40 additions & 14 deletions sample/confidential_client_mtls_pop_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,35 @@ def _print_result_summary(result, header):
print(header)
if "access_token" in result:
print(" acquired: yes")
print(" token_type is mtls_pop: ", result.get("token_type") == "mtls_pop")
print(" token_type: ", result.get("token_type"))
print(" carries binding_certificate: ", "binding_certificate" in result)
print(" served from cache: ", result.get("token_source") == "cache")
else:
print(" acquired: no")
print(" (inspect result['error'] / result['error_description'] to diagnose)")


def _call_resource(result):
resource_url = os.getenv("RESOURCE_URL")
if not resource_url or "access_token" not in result:
return
binding = result["binding_certificate"]
with binding.create_requests_session() as session:
response = session.get(
resource_url,
headers={
"Authorization":
"{} {}".format(
result["token_type"],
result["access_token"],
),
},
timeout=30,
)
response.raise_for_status()
print(" resource call status: ", response.status_code)


def vanilla_sni_mtls_pop():
"""Acquire an mTLS-bound PoP token using the SN/I cert as the TLS client cert."""
app = msal.ConfidentialClientApplication(
Expand All @@ -94,22 +115,27 @@ def vanilla_sni_mtls_pop():
)

_print_result_summary(result, "Vanilla SN/I -> mTLS PoP result:")
resource_url = os.getenv("RESOURCE_URL")
if resource_url and "access_token" in result:
binding = result["binding_certificate"]
with binding.create_requests_session() as session:
response = session.get(
resource_url,
headers={
"Authorization": "mtls_pop " + result["access_token"],
},
timeout=30,
)
response.raise_for_status()
print(" resource call status: ", response.status_code)
_call_resource(result)
return result


def bearer_over_mtls():
"""Acquire an ordinary Bearer token while authenticating over mTLS."""
credential = _build_cert_credential()
credential["send_certificate_over_mtls"] = True
app = msal.ConfidentialClientApplication(
os.environ["CLIENT_ID"],
authority=os.environ["AUTHORITY"],
client_credential=credential,
azure_region=os.getenv("AZURE_REGION"),
)
result = app.acquire_token_for_client(os.environ["SCOPE"].split())
_print_result_summary(result, "SN/I -> Bearer-over-mTLS result:")
return result


if __name__ == "__main__":
print("=== Vanilla SN/I -> mTLS PoP ===")
vanilla_sni_mtls_pop()
print("\n=== SN/I -> Bearer-over-mTLS ===")
bearer_over_mtls()
5 changes: 5 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ PoP acquisition with a certificate-bound Graph resource call and cache reuse,
and ordinary certificate Bearer authentication in `westus3`. The X509 matrix
uses client `163ffef9-a313-45b4-ab2f-c7e2f5e0e23e` in tenant
`bea21ebe-8b64-4d06-9f6d-6a889b120a7c`, matching the .NET and Go lab setup.

Bearer-over-mTLS also has a live client-credentials case for `westus3` and
`https://vault.azure.net/.default`. OBO, refresh-token, and authorization-code
coverage captures request construction only, matching the reference SDKs'
live-app enablement limitation; it does not prove live token issuance.
They do not require fetching unrelated user configuration from Key Vault.
Legacy interactive and other optional E2E skips are unchanged.

Expand Down
Loading
Loading