@@ -24,6 +24,11 @@ class ManagedIdentityError(ValueError):
2424 pass
2525
2626
27+ class MsiV2Error (ManagedIdentityError ):
28+ """Raised when the MSI v2 (mTLS PoP) flow fails."""
29+ pass
30+
31+
2732class ManagedIdentity (UserDict ):
2833 """Feed an instance of this class to :class:`msal.ManagedIdentityClient`
2934 to acquire token for the specified managed identity.
@@ -259,6 +264,8 @@ def acquire_token_for_client(
259264 * ,
260265 resource : str , # If/when we support scope, resource will become optional
261266 claims_challenge : Optional [str ] = None ,
267+ mtls_proof_of_possession : bool = False ,
268+ with_attestation_support : bool = False ,
262269 ):
263270 """Acquire token for the managed identity.
264271
@@ -278,6 +285,23 @@ def acquire_token_for_client(
278285 even if the app developer did not opt in for the "CP1" client capability.
279286 Upon receiving a `claims_challenge`, MSAL will attempt to acquire a new token.
280287
288+ :param bool mtls_proof_of_possession: (optional)
289+ When True, use the MSI v2 (mTLS Proof-of-Possession) flow to acquire an
290+ ``mtls_pop`` token bound to a short-lived mTLS certificate issued by the
291+ IMDS ``/issuecredential`` endpoint.
292+ Without this flag the legacy IMDS v1 flow is used.
293+ Defaults to False.
294+
295+ MSI v2 is used only when both ``mtls_proof_of_possession`` and
296+ ``with_attestation_support`` are True.
297+
298+ :param bool with_attestation_support: (optional)
299+ When True (and ``mtls_proof_of_possession`` is also True), attempt
300+ KeyGuard / platform attestation before credential issuance.
301+ On Windows this leverages ``AttestationClientLib.dll`` when available;
302+ on other platforms the parameter is silently ignored.
303+ Defaults to False.
304+
281305 .. note::
282306
283307 Known issue: When an Azure VM has only one user-assigned managed identity,
@@ -292,6 +316,27 @@ def acquire_token_for_client(
292316 client_id_in_cache = self ._managed_identity .get (
293317 ManagedIdentity .ID , "SYSTEM_ASSIGNED_MANAGED_IDENTITY" )
294318 now = time .time ()
319+ # MSI v2 is opt-in: use it only when BOTH mtls_proof_of_possession and
320+ # with_attestation_support are explicitly requested by the caller.
321+ # No auto-fallback: if MSI v2 is requested and fails, the error is raised.
322+ use_msi_v2 = bool (mtls_proof_of_possession and with_attestation_support )
323+
324+ if with_attestation_support and not mtls_proof_of_possession :
325+ raise ManagedIdentityError (
326+ "attestation_requires_pop" ,
327+ "with_attestation_support=True requires mtls_proof_of_possession=True (mTLS PoP)."
328+ )
329+
330+ if use_msi_v2 :
331+ from .msi_v2 import obtain_token as _obtain_token_v2
332+ result = _obtain_token_v2 (
333+ self ._http_client , self ._managed_identity , resource ,
334+ attestation_enabled = True ,
335+ )
336+ if "access_token" in result and "error" not in result :
337+ result [self ._TOKEN_SOURCE ] = self ._TOKEN_SOURCE_IDP
338+ return result
339+
295340 if True : # Attempt cache search even if receiving claims_challenge,
296341 # because we want to locate the existing token (if any) and refresh it
297342 matches = self ._token_cache .search (
0 commit comments