@@ -171,7 +171,6 @@ def __init__(
171171 token_cache = None ,
172172 http_cache = None ,
173173 client_capabilities : Optional [List [str ]] = None ,
174- msi_v2_enabled : Optional [bool ] = None ,
175174 ):
176175 """Create a managed identity client.
177176
@@ -213,17 +212,6 @@ def __init__(
213212 Client capability in Managed Identity is relayed as-is
214213 via ``xms_cc`` parameter on the wire.
215214
216- :param bool msi_v2_enabled: (optional)
217- Enable MSI v2 (mTLS PoP) token acquisition.
218- When True (or when the ``MSAL_ENABLE_MSI_V2`` environment variable
219- is set to a truthy value), the client will attempt to acquire tokens
220- using the MSI v2 flow (IMDS /issuecredential + mTLS PoP).
221- If the MSI v2 flow fails, it automatically falls back to MSI v1.
222- MSI v2 only applies to Azure VM (IMDS) environments; it is ignored
223- in other managed identity environments (App Service, Service Fabric,
224- Azure Arc, etc.).
225- Defaults to None (disabled unless the env var is set).
226-
227215 Recipe 1: Hard code a managed identity for your app::
228216
229217 import msal, requests
@@ -270,11 +258,6 @@ def __init__(
270258 )
271259 self ._token_cache = token_cache or TokenCache ()
272260 self ._client_capabilities = client_capabilities
273- # MSI v2 is enabled by the constructor param or the MSAL_ENABLE_MSI_V2 env var
274- if msi_v2_enabled is None :
275- env_val = os .environ .get ("MSAL_ENABLE_MSI_V2" , "" ).lower ()
276- msi_v2_enabled = env_val in ("1" , "true" , "yes" )
277- self ._msi_v2_enabled = msi_v2_enabled
278261
279262 def acquire_token_for_client (
280263 self ,
@@ -309,7 +292,8 @@ def acquire_token_for_client(
309292 Without this flag the legacy IMDS v1 flow is used.
310293 Defaults to False.
311294
312- This takes precedence over the ``msi_v2_enabled`` constructor parameter.
295+ MSI v2 is used only when both ``mtls_proof_of_possession`` and
296+ ``with_attestation_support`` are True.
313297
314298 :param bool with_attestation_support: (optional)
315299 When True (and ``mtls_proof_of_possession`` is also True), attempt
@@ -332,6 +316,27 @@ def acquire_token_for_client(
332316 client_id_in_cache = self ._managed_identity .get (
333317 ManagedIdentity .ID , "SYSTEM_ASSIGNED_MANAGED_IDENTITY" )
334318 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+
335340 if True : # Attempt cache search even if receiving claims_challenge,
336341 # because we want to locate the existing token (if any) and refresh it
337342 matches = self ._token_cache .search (
@@ -366,41 +371,13 @@ def acquire_token_for_client(
366371 break # With a fallback in hand, we break here to go refresh
367372 return access_token_from_cache # It is still good as new
368373 try :
369- result = None
370- # Per-call mtls_proof_of_possession takes precedence over the constructor
371- # default (msi_v2_enabled / MSAL_ENABLE_MSI_V2 env var).
372- use_msi_v2 = mtls_proof_of_possession or self ._msi_v2_enabled
373- if use_msi_v2 :
374- if mtls_proof_of_possession :
375- # Explicit per-call request: errors are raised, no fallback to v1
376- from .msi_v2 import obtain_token as _obtain_token_v2
377- result = _obtain_token_v2 (
378- self ._http_client , self ._managed_identity , resource ,
379- attestation_enabled = with_attestation_support )
380- logger .debug ("MSI v2 token acquisition succeeded" )
381- else :
382- # Legacy constructor flag: swallow errors and fall back to v1
383- try :
384- from .msi_v2 import obtain_token as _obtain_token_v2
385- result = _obtain_token_v2 (
386- self ._http_client , self ._managed_identity , resource ,
387- attestation_enabled = with_attestation_support )
388- logger .debug ("MSI v2 token acquisition succeeded" )
389- except MsiV2Error as exc :
390- logger .warning (
391- "MSI v2 flow failed, falling back to MSI v1: %s" , exc )
392- except Exception as exc : # pylint: disable=broad-except
393- logger .warning (
394- "MSI v2 encountered unexpected error, "
395- "falling back to MSI v1: %s" , exc )
396- if result is None :
397- result = _obtain_token (
398- self ._http_client , self ._managed_identity , resource ,
399- access_token_sha256_to_refresh = hashlib .sha256 (
400- access_token_to_refresh .encode ("utf-8" )).hexdigest ()
401- if access_token_to_refresh else None ,
402- client_capabilities = self ._client_capabilities ,
403- )
374+ result = _obtain_token (
375+ self ._http_client , self ._managed_identity , resource ,
376+ access_token_sha256_to_refresh = hashlib .sha256 (
377+ access_token_to_refresh .encode ("utf-8" )).hexdigest ()
378+ if access_token_to_refresh else None ,
379+ client_capabilities = self ._client_capabilities ,
380+ )
404381 if "access_token" in result :
405382 expires_in = result .get ("expires_in" , 3600 )
406383 if "refresh_in" not in result and expires_in >= 7200 :
@@ -753,4 +730,4 @@ def _obtain_token_on_arc(http_client, endpoint, resource):
753730 return {
754731 "error" : "invalid_request" ,
755732 "error_description" : response .text ,
756- }
733+ }
0 commit comments