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 83eeadf8..b470ae48 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 @@ -42,8 +42,6 @@ async def _async_acquire_token_for_client(msal_auth_client, *args, **kwargs): class MsalAuth(AccessTokenProviderBase): - _client_credential_cache = None - def __init__(self, msal_configuration: AgentAuthConfiguration): """Initializes the MsalAuth class with the given configuration. @@ -211,16 +209,38 @@ def _create_client_application( ) else: authority = MsalAuth._resolve_authority(self._msal_configuration, tenant_id) + client_credential = None - if self._client_credential_cache: - logger.info("Using cached client credentials for MSAL authentication.") - pass - elif self._msal_configuration.AUTH_TYPE == AuthTypes.client_secret: - self._client_credential_cache = self._msal_configuration.CLIENT_SECRET + if self._msal_configuration.AUTH_TYPE == AuthTypes.client_secret: + client_credential = self._msal_configuration.CLIENT_SECRET elif self._msal_configuration.AUTH_TYPE == AuthTypes.certificate: - self._client_credential_cache = { + client_credential = { "private_key_pfx_path": self._msal_configuration.CERT_PFX_FILE, } + elif self._msal_configuration.AUTH_TYPE == AuthTypes.federated_credentials: + mi_client = ManagedIdentityClient( + UserAssignedManagedIdentity( + client_id=self._msal_configuration.FEDERATED_CLIENT_ID + ), + http_client=Session(), + ) + + def get_assertion() -> str: + result = mi_client.acquire_token_for_client( + resource="api://AzureADTokenExchange" + ) + if "access_token" not in result: + logger.error( + f"Failed to acquire token for federated credentials: {result}" + ) + raise ValueError( + authentication_errors.FailedToAcquireToken.format( + str(result) + ) + ) + return result["access_token"] + + client_credential = {"client_assertion": get_assertion} else: logger.error( f"Unsupported authentication type: {self._msal_configuration.AUTH_TYPE}" @@ -232,7 +252,7 @@ def _create_client_application( return ConfidentialClientApplication( client_id=self._msal_configuration.CLIENT_ID, authority=authority, - client_credential=self._client_credential_cache, + client_credential=client_credential, ) def _client_rep( 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 abe77ea8..ee5798d7 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 @@ -2,7 +2,6 @@ # Licensed under the MIT License. from __future__ import annotations -from typing import Optional from microsoft_agents.hosting.core.authorization.auth_types import AuthTypes @@ -17,20 +16,22 @@ class AgentAuthConfiguration: CLIENT_SECRET: The client secret for the Azure AD application (if using client secret authentication). CERT_PFX_FILE: The path to the PFX certificate file (if using certificate authentication). CONNECTION_NAME: The name of the connection + FEDERATED_CLIENT_ID: The client ID for federated credentials (if using federated credentials authentication). SCOPES: The scopes to request AUTHORITY: The authority URL for the Azure AD (if different from the default). ALT_BLUEPRINT_ID: An optional alternative blueprint ID used when constructing a connector client. """ - TENANT_ID: Optional[str] + TENANT_ID: str | None AUTH_TYPE: AuthTypes - CLIENT_ID: Optional[str] - CLIENT_SECRET: Optional[str] - CERT_PFX_FILE: Optional[str] - CONNECTION_NAME: Optional[str] - SCOPES: Optional[list[str]] - AUTHORITY: Optional[str] - ALT_BLUEPRINT_ID: Optional[str] + CLIENT_ID: str | None + CLIENT_SECRET: str | None + CERT_PFX_FILE: str | None + CONNECTION_NAME: str | None + FEDERATED_CLIENT_ID: str | None + SCOPES: list[str] | None + AUTHORITY: str | None + ALT_BLUEPRINT_ID: str | None ANONYMOUS_ALLOWED: bool = False # Multi-connection support: Maintains a map of all configured connections @@ -50,6 +51,7 @@ def __init__( client_secret: str | None = None, cert_pfx_file: str | None = None, connection_name: str | None = None, + federated_client_id: str | None = None, authority: str | None = None, scopes: list[str] | None = None, anonymous_allowed: bool = False, @@ -63,6 +65,9 @@ def __init__( self.CLIENT_SECRET = client_secret or kwargs.get("CLIENTSECRET", None) self.CERT_PFX_FILE = cert_pfx_file or kwargs.get("CERTPFXFILE", None) self.CONNECTION_NAME = connection_name or kwargs.get("CONNECTIONNAME", None) + self.FEDERATED_CLIENT_ID = federated_client_id or kwargs.get( + "FEDERATEDCLIENTID", None + ) self.SCOPES = scopes or kwargs.get("SCOPES", None) self.ALT_BLUEPRINT_ID = kwargs.get("ALT_BLUEPRINT_NAME", None) self.ANONYMOUS_ALLOWED = anonymous_allowed or kwargs.get( 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 58784ae8..655630a4 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 @@ -10,3 +10,4 @@ class AuthTypes(str, Enum): client_secret = "ClientSecret" user_managed_identity = "UserManagedIdentity" system_managed_identity = "SystemManagedIdentity" + federated_credentials = "FederatedCredentials" diff --git a/tests/hosting_core/test_auth_configuration.py b/tests/hosting_core/test_auth_configuration.py index aadf7193..ca77a9c9 100644 --- a/tests/hosting_core/test_auth_configuration.py +++ b/tests/hosting_core/test_auth_configuration.py @@ -71,6 +71,7 @@ def test_empty_settings(self): assert auth_config.CLIENT_ID is None assert auth_config.CLIENT_SECRET is None assert auth_config.CERT_PFX_FILE is None + assert auth_config.FEDERATED_CLIENT_ID is None assert auth_config.CONNECTION_NAME is None assert auth_config.AUTHORITY is None assert auth_config.SCOPES is None