From 31cda3c7f7bbc1fc23e78cedbcf79b861dbc28df Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Tue, 4 Aug 2026 08:56:02 -0700 Subject: [PATCH 1/4] Workload identity initial draft --- .../authentication/msal/msal_auth.py | 13 +++++++++++++ .../core/authorization/agent_auth_configuration.py | 5 +++++ .../hosting/core/authorization/auth_types.py | 1 + 3 files changed, 19 insertions(+) diff --git a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py index 2a346ea6..7682159d 100644 --- a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py +++ b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py @@ -281,6 +281,19 @@ def get_assertion() -> str: ) return result["access_token"] + client_credential = {"client_assertion": get_assertion} + elif self._msal_configuration.AUTH_TYPE == AuthTypes.workload_identity: + if not self._msal_configuration.FEDERATED_TOKEN_FILE: + raise ValueError( + "FEDERATED_TOKEN_FILE must be set in configuration." + ) + + federated_token_file = self._msal_configuration.FEDERATED_TOKEN_FILE + + def get_assertion() -> str: + with open(federated_token_file, "rb") as f: + return f.read().decode("utf-8") + client_credential = {"client_assertion": get_assertion} else: logger.error( diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py index ff0cd0ae..ba86d068 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py @@ -97,6 +97,7 @@ class AgentAuthConfiguration: IDPM_RESOURCE: str | None ANONYMOUS_ALLOWED: bool = False VALIDATE_ISSUER: bool = False + FEDERATED_TOKEN_FILE: str | None # Provider-specific settings that aren't first-class fields (e.g. the Entra # sidecar's SERVICE_NAME, SIDECAR_BASE_URL). Preserved here as a single dict @@ -129,6 +130,7 @@ def __init__( anonymous_allowed: bool | None = None, issuers: list[str] | None = None, validate_issuer: bool | None = None, + federated_token_file: str | None = None, **kwargs: Any, ): @@ -148,6 +150,9 @@ def __init__( self.FEDERATED_CLIENT_ID = federated_client_id or kwargs.get( "FEDERATEDCLIENTID", None ) + self.FEDERATED_TOKEN_FILE = federated_token_file or kwargs.get( + "FEDERATEDTOKENFILE", None + ) self.SCOPES = scopes or kwargs.get("SCOPES", None) # Azure regional token service. Falls back to the legacy "REGIONALAUTHORITY" # configuration key when "AZUREREGION" is not provided. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/auth_types.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/auth_types.py index d61ec469..952ee898 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/auth_types.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/auth_types.py @@ -13,3 +13,4 @@ class AuthTypes(str, Enum): federated_credentials = "FederatedCredentials" identity_proxy_manager = "IdentityProxyManager" entra_auth_sidecar = "EntraAuthSideCar" + workload_identity = "WorkloadIdentity" From cc1526be524a81a38e185506fb8643529da677d7 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Tue, 4 Aug 2026 10:16:51 -0700 Subject: [PATCH 2/4] Adding unit tests --- .../authorization/agent_auth_configuration.py | 1 + tests/authentication_msal/test_msal_auth.py | 37 +++++++++++++++++++ tests/hosting_core/test_auth_configuration.py | 15 ++++++++ 3 files changed, 53 insertions(+) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py index ba86d068..410bcabb 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py @@ -27,6 +27,7 @@ "CERTPFXFILE", "CONNECTIONNAME", "FEDERATEDCLIENTID", + "FEDERATEDTOKENFILE", "SCOPES", "AZUREREGION", "REGIONALAUTHORITY", diff --git a/tests/authentication_msal/test_msal_auth.py b/tests/authentication_msal/test_msal_auth.py index adb5c140..e555acb6 100644 --- a/tests/authentication_msal/test_msal_auth.py +++ b/tests/authentication_msal/test_msal_auth.py @@ -260,6 +260,43 @@ def test_create_client_application_azure_region_defaults_none(self, mocker): assert mock_cca.call_args.kwargs["azure_region"] is None +class TestMsalAuthWorkloadIdentity: + def test_create_client_application_reads_projected_token(self, mocker, tmp_path): + token_file = tmp_path / "workload-token" + token_file.write_text("first-token", encoding="utf-8") + config = AgentAuthConfiguration( + auth_type=AuthTypes.workload_identity, + tenant_id="12345678-1234-1234-1234-123456789abc", + client_id="test-client-id", + federated_token_file=str(token_file), + ) + mock_cca = mocker.patch( + "microsoft_agents.authentication.msal.msal_auth.ConfidentialClientApplication" + ) + + MsalAuth(config)._create_client_application() + + client_assertion = mock_cca.call_args.kwargs["client_credential"][ + "client_assertion" + ] + assert client_assertion() == "first-token" + + token_file.write_text("refreshed-token", encoding="utf-8") + assert client_assertion() == "refreshed-token" + + def test_create_client_application_requires_token_file(self): + config = AgentAuthConfiguration( + auth_type=AuthTypes.workload_identity, + tenant_id="12345678-1234-1234-1234-123456789abc", + client_id="test-client-id", + ) + + with pytest.raises( + ValueError, match="FEDERATED_TOKEN_FILE must be set in configuration" + ): + MsalAuth(config)._create_client_application() + + class TestMsalAuthIdentityProxyManager: """ Test suite for the Identity Proxy Manager (IDPM) authentication type. diff --git a/tests/hosting_core/test_auth_configuration.py b/tests/hosting_core/test_auth_configuration.py index 2ef8a4c7..03d27e5e 100644 --- a/tests/hosting_core/test_auth_configuration.py +++ b/tests/hosting_core/test_auth_configuration.py @@ -102,6 +102,21 @@ def test_empty_settings(self): assert auth_config.SCOPES is None assert auth_config.AZURE_REGION is None + def test_workload_identity_token_file_from_kwargs(self): + auth_config = AgentAuthConfiguration( + AUTHTYPE="WorkloadIdentity", + CLIENTID="test-client-id", + TENANTID="test-tenant-id", + FEDERATEDTOKENFILE="/var/run/secrets/azure/tokens/azure-identity-token", + ) + + assert auth_config.AUTH_TYPE == AuthTypes.workload_identity + assert ( + auth_config.FEDERATED_TOKEN_FILE + == "/var/run/secrets/azure/tokens/azure-identity-token" + ) + assert "FEDERATEDTOKENFILE" not in auth_config.provider_settings + def test_azure_region_from_parameter(self): auth_config = AgentAuthConfiguration(azure_region="westus") assert auth_config.AZURE_REGION == "westus" From 1171152a519a58c4104b7b194c536b891a93bf81 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Tue, 4 Aug 2026 10:28:39 -0700 Subject: [PATCH 3/4] Updating changelog --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index 2c6e3c50..bdd5df0a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,8 @@ # Microsoft 365 Agents SDK for Python - Release Notes v1.4.0 (Unreleased) +## Major Features & Enhancements +- Added support for Workload Identity + ## New Models & APIs - **Regionalized UserTokenClient Support**: Added optional argument to `CloudAdapter` to configure Token Service endpoint used by `RestChannelServiceClientFactory` when creating `UserTokenClient` instances. From ec48288979086f3943781010a22a8beda4c320f7 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Tue, 4 Aug 2026 10:33:13 -0700 Subject: [PATCH 4/4] Another commit --- .../microsoft_agents/authentication/msal/msal_auth.py | 4 ++-- .../hosting/core/authorization/agent_auth_configuration.py | 2 ++ tests/authentication_msal/test_msal_auth.py | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py index 7682159d..81619e58 100644 --- a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py +++ b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py @@ -291,8 +291,8 @@ def get_assertion() -> str: federated_token_file = self._msal_configuration.FEDERATED_TOKEN_FILE def get_assertion() -> str: - with open(federated_token_file, "rb") as f: - return f.read().decode("utf-8") + with open(federated_token_file, encoding="utf-8") as f: + return f.read().strip() client_credential = {"client_assertion": get_assertion} else: diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py index 410bcabb..c9edd1d5 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/agent_auth_configuration.py @@ -82,6 +82,8 @@ class AgentAuthConfiguration: enforced by JwtTokenValidator (per issue #626) whenever the verified token's issuer is a recognized Entra issuer with a GUID tenant, regardless of this flag. + ANONYMOUS_ALLOWED: Whether anonymous access is allowed (default False). + FEDERATED_TOKEN_FILE: The path to the federated token file (if using federated credentials authentication). """ TENANT_ID: str | None diff --git a/tests/authentication_msal/test_msal_auth.py b/tests/authentication_msal/test_msal_auth.py index e555acb6..df51242e 100644 --- a/tests/authentication_msal/test_msal_auth.py +++ b/tests/authentication_msal/test_msal_auth.py @@ -263,7 +263,7 @@ def test_create_client_application_azure_region_defaults_none(self, mocker): class TestMsalAuthWorkloadIdentity: def test_create_client_application_reads_projected_token(self, mocker, tmp_path): token_file = tmp_path / "workload-token" - token_file.write_text("first-token", encoding="utf-8") + token_file.write_text(" first-token\r\n", encoding="utf-8") config = AgentAuthConfiguration( auth_type=AuthTypes.workload_identity, tenant_id="12345678-1234-1234-1234-123456789abc", @@ -281,7 +281,7 @@ def test_create_client_application_reads_projected_token(self, mocker, tmp_path) ] assert client_assertion() == "first-token" - token_file.write_text("refreshed-token", encoding="utf-8") + token_file.write_text("\trefreshed-token\n", encoding="utf-8") assert client_assertion() == "refreshed-token" def test_create_client_application_requires_token_file(self):