From ba47ebef2fa9c3223b39afaf06d5b6a69edcfe83 Mon Sep 17 00:00:00 2001 From: isra-fel <11371776+isra-fel@users.noreply.github.com> Date: Mon, 25 May 2026 15:22:18 +1000 Subject: [PATCH 1/2] broker sso --- src/azure-cli-core/azure/cli/core/_profile.py | 6 ++++- .../azure/cli/core/auth/agentic_session.py | 5 ++++ .../azure/cli/core/auth/identity.py | 23 ++++++++++++++++--- .../cli/command_modules/profile/__init__.py | 2 ++ .../cli/command_modules/profile/custom.py | 12 +++++++++- 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py index de0c8efe70f..e9ccd9e4f06 100644 --- a/src/azure-cli-core/azure/cli/core/_profile.py +++ b/src/azure-cli-core/azure/cli/core/_profile.py @@ -148,6 +148,7 @@ def login(self, is_service_principal, tenant, scopes=None, + use_broker_sso=False, use_device_code=False, allow_no_subscriptions=False, use_cert_sn_issuer=None, @@ -176,7 +177,10 @@ def login(self, if use_device_code: user_identity = identity.login_with_device_code(scopes=scopes, claims_challenge=claims_challenge) else: - user_identity = identity.login_with_auth_code(scopes=scopes, claims_challenge=claims_challenge) + user_identity = identity.login_with_auth_code( + scopes=scopes, + claims_challenge=claims_challenge, + use_broker_sso=use_broker_sso) else: if not is_service_principal: user_identity = identity.login_with_username_password(username, password, scopes=scopes) diff --git a/src/azure-cli-core/azure/cli/core/auth/agentic_session.py b/src/azure-cli-core/azure/cli/core/auth/agentic_session.py index 33112322331..6ceed7cd3bf 100644 --- a/src/azure-cli-core/azure/cli/core/auth/agentic_session.py +++ b/src/azure-cli-core/azure/cli/core/auth/agentic_session.py @@ -26,6 +26,11 @@ COPILOT_AGENT_SESSION_ID = "COPILOT_AGENT_SESSION_ID" +def is_agentic_session(): + """Determine if we're in an agentic session based on the presence of COPILOT_AGENT_SESSION_ID.""" + return os.environ.get(COPILOT_AGENT_SESSION_ID) is not None + + def build_agentic_session_params(): """Read COPILOT_AGENT_SESSION_ID and build the agentic claims challenge. diff --git a/src/azure-cli-core/azure/cli/core/auth/identity.py b/src/azure-cli-core/azure/cli/core/auth/identity.py index 91629e89441..e1265d3cb40 100644 --- a/src/azure-cli-core/azure/cli/core/auth/identity.py +++ b/src/azure-cli-core/azure/cli/core/auth/identity.py @@ -3,6 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +import cmd import json import os import re @@ -145,7 +146,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, claims_challenge=None): + def login_with_auth_code(self, scopes, claims_challenge=None, use_broker_sso=False): # Emit a warning to inform that a browser is opened. # Only show the path part of the URL and hide the query string. @@ -161,15 +162,31 @@ def _prompt_launching_ui(ui=None, **_): from .util import read_response_templates success_template, error_template = read_response_templates() + from .agentic_session import is_agentic_session + is_agentic_session_value = is_agentic_session() + enable_broker_on_windows = cmd.cli_ctx.config.getboolean('core', 'enable_broker_on_windows', fallback=True) + # Broker is available only on Windows for now + broker_sso_available = sys.platform.startswith('win') and enable_broker_on_windows + logger.debug("use_broker_sso: %s, is_agentic_session: %s, broker_sso_available: %s", + use_broker_sso, is_agentic_session_value, broker_sso_available) + use_broker_sso = (use_broker_sso or is_agentic_session_value) and broker_sso_available + prompt = 'none' if use_broker_sso else 'select_account' + # For AAD, use port 0 to let the system choose arbitrary unused ephemeral port to avoid port collision # on port 8400 from the old design. However, ADFS only allows port 8400. result = self._msal_app.acquire_token_interactive( - scopes, prompt='select_account', port=8400 if self._is_adfs else None, + scopes, + prompt=prompt, + port=8400 if self._is_adfs else 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, claims_challenge=claims_challenge) - return check_result(result) + parsed = check_result(result) + if use_broker_sso: + # log parsed result in debug level + logger.debug("Result from broker SSO login: %s", json.dumps(parsed, indent=4)) + return parsed def login_with_device_code(self, scopes, claims_challenge=None): flow = self._msal_app.initiate_device_flow(scopes, claims_challenge=claims_challenge) diff --git a/src/azure-cli/azure/cli/command_modules/profile/__init__.py b/src/azure-cli/azure/cli/command_modules/profile/__init__.py index af25643541d..f19bab58c48 100644 --- a/src/azure-cli/azure/cli/command_modules/profile/__init__.py +++ b/src/azure-cli/azure/cli/command_modules/profile/__init__.py @@ -58,6 +58,8 @@ def load_arguments(self, command): c.argument('claims_challenge', help="Base64-encoded claims challenge requested by a resource API in the " "WWW-Authenticate header.") + c.argument('use_broker_sso', action='store_true', + help="(Experimental) Enable broker SSO for login.") c.ignore('_subscription') # hide the global subscription parameter # Skip subscription discovery diff --git a/src/azure-cli/azure/cli/command_modules/profile/custom.py b/src/azure-cli/azure/cli/command_modules/profile/custom.py index 164a362a054..c02be79b17a 100644 --- a/src/azure-cli/azure/cli/command_modules/profile/custom.py +++ b/src/azure-cli/azure/cli/command_modules/profile/custom.py @@ -134,7 +134,7 @@ def _select_and_set_active(profile, subscriptions): # 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, + claims_challenge=None, use_broker_sso=False, # Device code flow use_device_code=False, # Service principal @@ -169,6 +169,15 @@ def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_ logger.warning(USERNAME_PASSWORD_DEPRECATION_WARNING_AZURE_CLOUD) else: logger.warning(USERNAME_PASSWORD_DEPRECATION_WARNING_OTHER_CLOUD) + # broker_sso incompatible with workload identities + # broker_sso relies on the presence of a broker, which is only available on Windows (for now) + enable_broker_on_windows = cmd.cli_ctx.config.getboolean('core', 'enable_broker_on_windows', fallback=True) + broker_sso_available = sys.platform.startswith('win') and enable_broker_on_windows + if use_broker_sso and not broker_sso_available: + raise CLIError("usage error: '--use-broker-sso' is only supported on Windows with broker enabled") + if use_broker_sso and (service_principal or identity): + raise CLIError("usage error: '--use-broker-sso' is not applicable " + "to service principal or managed identity login") if claims_challenge: from azure.cli.core.util import b64decode @@ -219,6 +228,7 @@ def login(cmd, username=None, password=None, tenant=None, scopes=None, allow_no_ service_principal, tenant, scopes=scopes, + use_broker_sso=use_broker_sso, use_device_code=use_device_code, allow_no_subscriptions=allow_no_subscriptions, use_cert_sn_issuer=use_cert_sn_issuer, From 84c7a55fd4fa5752983be45fea46437a8adcd5eb Mon Sep 17 00:00:00 2001 From: isra-fel <11371776+isra-fel@users.noreply.github.com> Date: Mon, 25 May 2026 16:45:43 +1000 Subject: [PATCH 2/2] [Identity] Refactor broker SSO availability check to use instance variable --- src/azure-cli-core/azure/cli/core/auth/identity.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/auth/identity.py b/src/azure-cli-core/azure/cli/core/auth/identity.py index e1265d3cb40..ec690b03ac7 100644 --- a/src/azure-cli-core/azure/cli/core/auth/identity.py +++ b/src/azure-cli-core/azure/cli/core/auth/identity.py @@ -3,7 +3,6 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -import cmd import json import os import re @@ -164,9 +163,8 @@ def _prompt_launching_ui(ui=None, **_): from .agentic_session import is_agentic_session is_agentic_session_value = is_agentic_session() - enable_broker_on_windows = cmd.cli_ctx.config.getboolean('core', 'enable_broker_on_windows', fallback=True) # Broker is available only on Windows for now - broker_sso_available = sys.platform.startswith('win') and enable_broker_on_windows + broker_sso_available = sys.platform.startswith('win') and self._enable_broker_on_windows logger.debug("use_broker_sso: %s, is_agentic_session: %s, broker_sso_available: %s", use_broker_sso, is_agentic_session_value, broker_sso_available) use_broker_sso = (use_broker_sso or is_agentic_session_value) and broker_sso_available