Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions src/azure-cli-core/azure/cli/core/_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def login(self,
allow_no_subscriptions=False,
use_cert_sn_issuer=None,
show_progress=False,
**kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR removes lots of kwargs. I just want to confirm that kwargs is never used and always empty, right?

Copy link
Member Author

@jiasli jiasli Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. It is not used by any means. See the explanation in the PR description #31778 (comment).

claims_challenge=None):
"""
For service principal, `password` is a dict returned by ServicePrincipalAuth.build_credential
"""
Expand All @@ -172,12 +172,12 @@ def login(self,
use_device_code = True

if use_device_code:
user_identity = identity.login_with_device_code(scopes=scopes, **kwargs)
user_identity = identity.login_with_device_code(scopes=scopes)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initiate_device_flow doesn't support claims_challenge and fails with

  File "d:\cli\azure-cli\src\azure-cli-core\azure\cli\core\auth\identity.py", line 176, in login_with_device_code
    flow = self._msal_app.initiate_device_flow(scopes, claims_challenge=claims_challenge)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\cli\py312\Lib\site-packages\msal\application.py", line 2341, in initiate_device_flow
    flow = self.client.initiate_device_flow(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\cli\py312\Lib\site-packages\msal\oauth2cli\oauth2.py", line 326, in initiate_device_flow
    resp = self._http_client.post(self.configuration[DAE],
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\cli\py312\Lib\site-packages\msal\individual_cache.py", line 273, in wrapper
    value = function(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\cli\py312\Lib\site-packages\msal\individual_cache.py", line 273, in wrapper
    value = function(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\cli\py312\Lib\site-packages\msal\throttled_http_client.py", line 96, in post
    return NormalizedResponse(self.http_client.post(*args, **kwargs))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\cli\py312\Lib\site-packages\requests\sessions.py", line 637, in post
    return self.request("POST", url, data=data, json=json, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Session.request() got an unexpected keyword argument 'claims_challenge'

If claims_challenge is not passed to initiate_device_flow, calling acquire_token_by_device_flow with claims_challenge fails:

  File "d:\cli\azure-cli\src\azure-cli-core\azure\cli\core\auth\identity.py", line 186, in login_with_device_code
    return check_result(result)
           ^^^^^^^^^^^^^^^^^^^^
  File "d:\cli\azure-cli\src\azure-cli-core\azure\cli\core\auth\util.py", line 134, in check_result
    aad_error_handler(result, **kwargs)
  File "d:\cli\azure-cli\src\azure-cli-core\azure\cli\core\auth\util.py", line 53, in aad_error_handler
    raise AuthenticationError(error_description, msal_error=error, recommendation=recommendation)
azure.cli.core.azclierror.AuthenticationError: AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '797f4846-ba00-4fd7-ba43-dac1f8f63013'. Trace ID: 9416cbef-6f2f-4bba-b99f-e41722113e00 Correlation ID: d657b456-40d3-4d39-ae98-4d2927752bb8 Timestamp: 2025-06-24 07:56:44Z

This has been reported to MSAL:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we show a warning telling customers that --claims-challenge is not supported for device code flow and will be ignored?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a temporary bug in MSAL's device code implementation. I prefer not to complicate things as the bug will be fixed eventually.

else:
user_identity = identity.login_with_auth_code(scopes=scopes, **kwargs)
user_identity = identity.login_with_auth_code(scopes=scopes, claims_challenge=claims_challenge)
else:
if not is_service_principal:
user_identity = identity.login_with_username_password(username, password, scopes=scopes, **kwargs)
user_identity = identity.login_with_username_password(username, password, scopes=scopes)
else:
identity.login_with_service_principal(username, password, scopes=scopes)

Expand Down
14 changes: 7 additions & 7 deletions src/azure-cli-core/azure/cli/core/auth/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def _service_principal_store(self):
Identity._service_principal_store_instance = ServicePrincipalStore(store)
return Identity._service_principal_store_instance

def login_with_auth_code(self, scopes, **kwargs):
def login_with_auth_code(self, scopes, claims_challenge=None):
# Emit a warning to inform that a browser is opened.
# Only show the path part of the URL and hide the query string.

Expand All @@ -168,21 +168,21 @@ def _prompt_launching_ui(ui=None, **_):
success_template=success_template, error_template=error_template,
parent_window_handle=self._msal_app.CONSOLE_WINDOW_HANDLE, on_before_launching_ui=_prompt_launching_ui,
enable_msa_passthrough=True,
**kwargs)
claims_challenge=claims_challenge)
return check_result(result)

def login_with_device_code(self, scopes, **kwargs):
flow = self._msal_app.initiate_device_flow(scopes, **kwargs)
def login_with_device_code(self, scopes):
flow = self._msal_app.initiate_device_flow(scopes)
if "user_code" not in flow:
raise ValueError(
"Fail to create device flow. Err: %s" % json.dumps(flow, indent=4))
from azure.cli.core.style import print_styled_text, Style
print_styled_text((Style.WARNING, flow["message"]), file=sys.stderr)
result = self._msal_app.acquire_token_by_device_flow(flow, **kwargs) # By default it will block
result = self._msal_app.acquire_token_by_device_flow(flow) # By default it will block
return check_result(result)

def login_with_username_password(self, username, password, scopes, **kwargs):
result = self._msal_app.acquire_token_by_username_password(username, password, scopes, **kwargs)
def login_with_username_password(self, username, password, scopes):
result = self._msal_app.acquire_token_by_username_password(username, password, scopes)
return check_result(result)

def login_with_service_principal(self, client_id, credential, scopes):
Expand Down
15 changes: 13 additions & 2 deletions src/azure-cli-core/azure/cli/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,13 +603,24 @@ def shell_safe_json_parse(json_or_dict_string, preserve_order=False, strict=True

def b64encode(s):
"""
Encodes a string to base64 on 2.x and 3.x
Encodes a string to a base64 string.
:param str s: latin_1 encoded string
:return: base64 encoded string
:rtype: str
"""
encoded = base64.b64encode(s.encode("latin-1"))
return encoded if encoded is str else encoded.decode('latin-1')
return encoded.decode('latin-1')


def b64decode(s):
"""
Decodes a base64 string to a string.
:param str s: latin_1 encoded base64 string
:return: decoded string
:rtype: str
"""
encoded = base64.b64decode(s.encode("latin-1"))
return encoded.decode('latin-1')


def b64_to_hex(s):
Expand Down
3 changes: 3 additions & 0 deletions src/azure-cli/azure/cli/command_modules/profile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ def load_arguments(self, command):
c.argument('allow_no_subscriptions', action='store_true',
help="Support accessing tenants without subscriptions. It's useful to run "
"tenant-level commands, such as 'az ad'.")
c.argument('claims_challenge',
help="Base64-encoded claims challenge requested by a resource API in the "
"WWW-Authenticate header.")
c.ignore('_subscription') # hide the global subscription parameter

# Device code flow
Expand Down
9 changes: 8 additions & 1 deletion src/azure-cli/azure/cli/command_modules/profile/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def account_clear(cmd):

# pylint: disable=too-many-branches, too-many-locals
def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_subscriptions=False,
claims_challenge=None,
# Device code flow
use_device_code=False,
# Service principal
Expand All @@ -148,6 +149,10 @@ def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_
else:
logger.warning(USERNAME_PASSWORD_DEPRECATION_WARNING_OTHER_CLOUD)

if claims_challenge:
from azure.cli.core.util import b64decode
claims_challenge = b64decode(claims_challenge)

interactive = False

profile = Profile(cli_ctx=cmd.cli_ctx)
Expand Down Expand Up @@ -194,7 +199,9 @@ def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_
use_device_code=use_device_code,
allow_no_subscriptions=allow_no_subscriptions,
use_cert_sn_issuer=use_cert_sn_issuer,
show_progress=select_subscription)
show_progress=select_subscription,
claims_challenge=claims_challenge
)

# Launch interactive account selection. No JSON output.
if select_subscription:
Expand Down
Loading