@@ -182,6 +182,10 @@ class ClientApplication(object):
182182 _TOKEN_SOURCE_BROKER = "broker"
183183
184184 _enable_broker = False
185+ _AUTH_SCHEME_UNSUPPORTED = (
186+ "auth_scheme is currently only available from broker. "
187+ "You can enable broker by following these instructions. "
188+ "https://msal-python.readthedocs.io/en/latest/#publicclientapplication" )
185189
186190 def __init__ (
187191 self , client_id ,
@@ -557,6 +561,10 @@ def _decide_broker(self, allow_broker, enable_pii_log):
557561 "We will fallback to non-broker." )
558562 logger .debug ("Broker enabled? %s" , self ._enable_broker )
559563
564+ def is_pop_supported (self ):
565+ """Returns True if this client supports Proof-of-Possession Access Token."""
566+ return self ._enable_broker
567+
560568 def _decorate_scope (
561569 self , scopes ,
562570 reserved_scope = frozenset (['openid' , 'profile' , 'offline_access' ])):
@@ -1185,6 +1193,7 @@ def acquire_token_silent(
11851193 authority = None , # See get_authorization_request_url()
11861194 force_refresh = False , # type: Optional[boolean]
11871195 claims_challenge = None ,
1196+ auth_scheme = None ,
11881197 ** kwargs ):
11891198 """Acquire an access token for given account, without user interaction.
11901199
@@ -1205,7 +1214,7 @@ def acquire_token_silent(
12051214 return None # A backward-compatible NO-OP to drop the account=None usage
12061215 result = _clean_up (self ._acquire_token_silent_with_error (
12071216 scopes , account , authority = authority , force_refresh = force_refresh ,
1208- claims_challenge = claims_challenge , ** kwargs ))
1217+ claims_challenge = claims_challenge , auth_scheme = auth_scheme , ** kwargs ))
12091218 return result if result and "error" not in result else None
12101219
12111220 def acquire_token_silent_with_error (
@@ -1215,6 +1224,7 @@ def acquire_token_silent_with_error(
12151224 authority = None , # See get_authorization_request_url()
12161225 force_refresh = False , # type: Optional[boolean]
12171226 claims_challenge = None ,
1227+ auth_scheme = None ,
12181228 ** kwargs ):
12191229 """Acquire an access token for given account, without user interaction.
12201230
@@ -1241,6 +1251,12 @@ def acquire_token_silent_with_error(
12411251 in the form of a claims_challenge directive in the www-authenticate header to be
12421252 returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token.
12431253 It is a string of a JSON object which contains lists of claims being requested from these locations.
1254+ :param object auth_scheme:
1255+ You can provide an ``msal.auth_scheme.PopAuthScheme`` object
1256+ so that MSAL will get a Proof-of-Possession (POP) token for you.
1257+
1258+ New in version 1.26.0.
1259+
12441260 :return:
12451261 - A dict containing no "error" key,
12461262 and typically contains an "access_token" key,
@@ -1252,7 +1268,7 @@ def acquire_token_silent_with_error(
12521268 return None # A backward-compatible NO-OP to drop the account=None usage
12531269 return _clean_up (self ._acquire_token_silent_with_error (
12541270 scopes , account , authority = authority , force_refresh = force_refresh ,
1255- claims_challenge = claims_challenge , ** kwargs ))
1271+ claims_challenge = claims_challenge , auth_scheme = auth_scheme , ** kwargs ))
12561272
12571273 def _acquire_token_silent_with_error (
12581274 self ,
@@ -1261,6 +1277,7 @@ def _acquire_token_silent_with_error(
12611277 authority = None , # See get_authorization_request_url()
12621278 force_refresh = False , # type: Optional[boolean]
12631279 claims_challenge = None ,
1280+ auth_scheme = None ,
12641281 ** kwargs ):
12651282 assert isinstance (scopes , list ), "Invalid parameter type"
12661283 self ._validate_ssh_cert_input_data (kwargs .get ("data" , {}))
@@ -1276,6 +1293,7 @@ def _acquire_token_silent_with_error(
12761293 scopes , account , self .authority , force_refresh = force_refresh ,
12771294 claims_challenge = claims_challenge ,
12781295 correlation_id = correlation_id ,
1296+ auth_scheme = auth_scheme ,
12791297 ** kwargs )
12801298 if result and "error" not in result :
12811299 return result
@@ -1298,6 +1316,7 @@ def _acquire_token_silent_with_error(
12981316 scopes , account , the_authority , force_refresh = force_refresh ,
12991317 claims_challenge = claims_challenge ,
13001318 correlation_id = correlation_id ,
1319+ auth_scheme = auth_scheme ,
13011320 ** kwargs )
13021321 if result :
13031322 if "error" not in result :
@@ -1322,12 +1341,13 @@ def _acquire_token_silent_from_cache_and_possibly_refresh_it(
13221341 claims_challenge = None ,
13231342 correlation_id = None ,
13241343 http_exceptions = None ,
1344+ auth_scheme = None ,
13251345 ** kwargs ):
13261346 # This internal method has two calling patterns:
13271347 # it accepts a non-empty account to find token for a user,
13281348 # and accepts account=None to find a token for the current app.
13291349 access_token_from_cache = None
1330- if not (force_refresh or claims_challenge ): # Bypass AT when desired or using claims
1350+ if not (force_refresh or claims_challenge or auth_scheme ): # Then attempt AT cache
13311351 query = {
13321352 "client_id" : self .client_id ,
13331353 "environment" : authority .instance ,
@@ -1370,6 +1390,8 @@ def _acquire_token_silent_from_cache_and_possibly_refresh_it(
13701390 try :
13711391 data = kwargs .get ("data" , {})
13721392 if account and account .get ("authority_type" ) == _AUTHORITY_TYPE_CLOUDSHELL :
1393+ if auth_scheme :
1394+ raise ValueError ("auth_scheme is not supported in Cloud Shell" )
13731395 return self ._acquire_token_by_cloud_shell (scopes , data = data )
13741396
13751397 if self ._enable_broker and account and account .get ("account_source" ) in (
@@ -1385,6 +1407,7 @@ def _acquire_token_silent_from_cache_and_possibly_refresh_it(
13851407 claims = _merge_claims_challenge_and_capabilities (
13861408 self ._client_capabilities , claims_challenge ),
13871409 correlation_id = correlation_id ,
1410+ auth_scheme = auth_scheme ,
13881411 ** data )
13891412 if response : # Broker provides a decisive outcome
13901413 account_was_established_by_broker = account .get (
@@ -1393,6 +1416,8 @@ def _acquire_token_silent_from_cache_and_possibly_refresh_it(
13931416 if account_was_established_by_broker or broker_attempt_succeeded_just_now :
13941417 return self ._process_broker_response (response , scopes , data )
13951418
1419+ if auth_scheme :
1420+ raise ValueError (self ._AUTH_SCHEME_UNSUPPORTED )
13961421 if account :
13971422 result = self ._acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family (
13981423 authority , self ._decorate_scope (scopes ), account ,
@@ -1588,7 +1613,11 @@ def acquire_token_by_refresh_token(self, refresh_token, scopes, **kwargs):
15881613 return response
15891614
15901615 def acquire_token_by_username_password (
1591- self , username , password , scopes , claims_challenge = None , ** kwargs ):
1616+ self , username , password , scopes , claims_challenge = None ,
1617+ # Note: We shouldn't need to surface enable_msa_passthrough,
1618+ # because this ROPC won't work with MSA account anyway.
1619+ auth_scheme = None ,
1620+ ** kwargs ):
15921621 """Gets a token for a given resource via user credentials.
15931622
15941623 See this page for constraints of Username Password Flow.
@@ -1604,6 +1633,12 @@ def acquire_token_by_username_password(
16041633 returned from the UserInfo Endpoint and/or in the ID Token and/or Access Token.
16051634 It is a string of a JSON object which contains lists of claims being requested from these locations.
16061635
1636+ :param object auth_scheme:
1637+ You can provide an ``msal.auth_scheme.PopAuthScheme`` object
1638+ so that MSAL will get a Proof-of-Possession (POP) token for you.
1639+
1640+ New in version 1.26.0.
1641+
16071642 :return: A dict representing the json response from AAD:
16081643
16091644 - A successful response would contain "access_token" key,
@@ -1623,9 +1658,12 @@ def acquire_token_by_username_password(
16231658 self .authority ._is_known_to_developer
16241659 or self ._instance_discovery is False ) else None ,
16251660 claims = claims ,
1661+ auth_scheme = auth_scheme ,
16261662 )
16271663 return self ._process_broker_response (response , scopes , kwargs .get ("data" , {}))
16281664
1665+ if auth_scheme :
1666+ raise ValueError (self ._AUTH_SCHEME_UNSUPPORTED )
16291667 scopes = self ._decorate_scope (scopes )
16301668 telemetry_context = self ._build_telemetry_context (
16311669 self .ACQUIRE_TOKEN_BY_USERNAME_PASSWORD_ID )
@@ -1768,6 +1806,7 @@ def acquire_token_interactive(
17681806 max_age = None ,
17691807 parent_window_handle = None ,
17701808 on_before_launching_ui = None ,
1809+ auth_scheme = None ,
17711810 ** kwargs ):
17721811 """Acquire token interactively i.e. via a local browser.
17731812
@@ -1843,6 +1882,12 @@ def acquire_token_interactive(
18431882
18441883 New in version 1.20.0.
18451884
1885+ :param object auth_scheme:
1886+ You can provide an ``msal.auth_scheme.PopAuthScheme`` object
1887+ so that MSAL will get a Proof-of-Possession (POP) token for you.
1888+
1889+ New in version 1.26.0.
1890+
18461891 :return:
18471892 - A dict containing no "error" key,
18481893 and typically contains an "access_token" key.
@@ -1887,12 +1932,15 @@ def acquire_token_interactive(
18871932 claims ,
18881933 data ,
18891934 on_before_launching_ui ,
1935+ auth_scheme ,
18901936 prompt = prompt ,
18911937 login_hint = login_hint ,
18921938 max_age = max_age ,
18931939 )
18941940 return self ._process_broker_response (response , scopes , data )
18951941
1942+ if auth_scheme :
1943+ raise ValueError ("auth_scheme is currently only available from broker" )
18961944 on_before_launching_ui (ui = "browser" )
18971945 telemetry_context = self ._build_telemetry_context (
18981946 self .ACQUIRE_TOKEN_INTERACTIVE )
@@ -1927,6 +1975,7 @@ def _acquire_token_interactive_via_broker(
19271975 claims , # type: str
19281976 data , # type: dict
19291977 on_before_launching_ui , # type: callable
1978+ auth_scheme , # type: object
19301979 prompt = None ,
19311980 login_hint = None , # type: Optional[str]
19321981 max_age = None ,
@@ -1950,6 +1999,7 @@ def _acquire_token_interactive_via_broker(
19501999 accounts [0 ]["local_account_id" ],
19512000 scopes ,
19522001 claims = claims ,
2002+ auth_scheme = auth_scheme ,
19532003 ** data )
19542004 if response and "error" not in response :
19552005 return response
@@ -1962,6 +2012,7 @@ def _acquire_token_interactive_via_broker(
19622012 claims = claims ,
19632013 max_age = max_age ,
19642014 enable_msa_pt = enable_msa_passthrough ,
2015+ auth_scheme = auth_scheme ,
19652016 ** data )
19662017 is_wrong_account = bool (
19672018 # _signin_silently() only gets tokens for default account,
@@ -2002,6 +2053,7 @@ def _acquire_token_interactive_via_broker(
20022053 claims = claims ,
20032054 max_age = max_age ,
20042055 enable_msa_pt = enable_msa_passthrough ,
2056+ auth_scheme = auth_scheme ,
20052057 ** data )
20062058
20072059 def initiate_device_flow (self , scopes = None , ** kwargs ):
0 commit comments