diff --git a/python/samples/concepts/setup/ALL_SETTINGS.md b/python/samples/concepts/setup/ALL_SETTINGS.md
index db477e4b0e12..9bd03d72ed30 100644
--- a/python/samples/concepts/setup/ALL_SETTINGS.md
+++ b/python/samples/concepts/setup/ALL_SETTINGS.md
@@ -49,7 +49,7 @@
| | [AzureResponsesAgent](../../../semantic_kernel/agents/open_ai/azure_responses_agent.py) | deployment_name,
api_key,
endpoint,
api_version,
base_url | AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME,
AZURE_OPENAI_API_KEY,
AZURE_OPENAI_ENDPOINT,
AZURE_OPENAI_API_VERSION,
AZURE_OPENAI_BASE_URL | Yes,
No,
Yes,
Yes,
No | [AzureOpenAISettings](../../../semantic_kernel/connectors/ai/open_ai/settings/azure_open_ai_settings.py) |
| Azure AI | [AzureAIAgent](../../../semantic_kernel/agents/azure_ai/azure_ai_agent.py) | model_deployment_name,
endpoint,
agent_id,
bing_connection_id,
azure_ai_search_connection_id,
azure_ai_search_index_name,
api_version | AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME,
AZURE_AI_AGENT_ENDPOINT,
AZURE_AI_AGENT_AGENT_ID,
AZURE_AI_AGENT_BING_CONNECTION_ID,
AZURE_AI_AGENT_AZURE_AI_SEARCH_CONNECTION_ID,
AZURE_AI_AGENT_AZURE_AI_SEARCH_INDEX_NAME,
AZURE_AI_AGENT_API_VERSION | Yes,
Yes,
No,
No,
No,
No,
No | [AzureAIAgentSettings](../../../semantic_kernel/agents/azure_ai/azure_ai_agent_settings.py) |
| Bedrock | [BedrockAgent](../../../semantic_kernel/agents/bedrock/bedrock_agent.py) | agent_resource_role_arn,
foundation_model | BEDROCK_AGENT_AGENT_RESOURCE_ROLE_ARN,
BEDROCK_AGENT_FOUNDATION_MODEL | Yes,
Yes | [BedrockAgentSettings](../../../semantic_kernel/agents/bedrock/bedrock_agent_settings.py) |
-| Copilot Studio | [CopilotStudioAgent](../../../semantic_kernel/agents/copilot_studio/copilot_studio_agent.py) | app_client_id,
tenant_id,
environment_id,
agent_identifier,
cloud,
copilot_agent_type,
custom_power_platform_cloud,
client_secret,
client_certificate,
user_assertion,
auth_mode | COPILOT_STUDIO_AGENT_APP_CLIENT_ID,
COPILOT_STUDIO_AGENT_TENANT_ID,
COPILOT_STUDIO_AGENT_ENVIRONMENT_ID,
COPILOT_STUDIO_AGENT_AGENT_IDENTIFIER,
COPILOT_STUDIO_AGENT_CLOUD,
COPILOT_STUDIO_AGENT_COPILOT_AGENT_TYPE,
COPILOT_STUDIO_AGENT_CUSTOM_POWER_PLATFORM_CLOUD,
COPILOT_STUDIO_AGENT_CLIENT_SECRET,
COPILOT_STUDIO_AGENT_CLIENT_CERTIFICATE,
COPILOT_STUDIO_AGENT_USER_ASSERTION,
COPILOT_STUDIO_AGENT_AUTH_MODE | No (varies by mode) | [CopilotStudioAgentSettings](../../../semantic_kernel/agents/copilot_studio/copilot_studio_agent_settings.py) |
+| Copilot Studio | [CopilotStudioAgent](../../../semantic_kernel/agents/copilot_studio/copilot_studio_agent.py) | app_client_id,
tenant_id,
environment_id,
agent_identifier,
cloud,
copilot_agent_type,
custom_power_platform_cloud,
user_assertion,
auth_mode | COPILOT_STUDIO_AGENT_APP_CLIENT_ID,
COPILOT_STUDIO_AGENT_TENANT_ID,
COPILOT_STUDIO_AGENT_ENVIRONMENT_ID,
COPILOT_STUDIO_AGENT_AGENT_IDENTIFIER,
COPILOT_STUDIO_AGENT_CLOUD,
COPILOT_STUDIO_AGENT_COPILOT_AGENT_TYPE,
COPILOT_STUDIO_AGENT_CUSTOM_POWER_PLATFORM_CLOUD,
COPILOT_STUDIO_AGENT_USER_ASSERTION,
COPILOT_STUDIO_AGENT_AUTH_MODE | No (varies by configuration) | [CopilotStudioAgentSettings](../../../semantic_kernel/agents/copilot_studio/copilot_studio_agent_settings.py) |
diff --git a/python/samples/getting_started_with_agents/copilot_studio/README.md b/python/samples/getting_started_with_agents/copilot_studio/README.md
index 5df7248f3f44..5918a7327ec1 100644
--- a/python/samples/getting_started_with_agents/copilot_studio/README.md
+++ b/python/samples/getting_started_with_agents/copilot_studio/README.md
@@ -84,10 +84,6 @@ COPILOT_STUDIO_AGENT_AGENT_IDENTIFIER=
COPILOT_STUDIO_AGENT_AUTH_MODE=interactive
```
-## Create an Application Registration in Entra ID – Service Principal Login
-
-> **Warning**: Service Principal login is **not yet supported** in the current version of the `CopilotStudioClient`.
-
## Creating a `CopilotStudioAgent` Client
If all required environment variables are set correctly, you don't need to manually create or pass a `client`. Semantic Kernel will automatically construct the client using the environment configuration.
@@ -96,11 +92,9 @@ However, if you need to override any environment variables—such as when specif
```python
client: CopilotClient = CopilotStudioAgent.create_client(
- auth_mode: CopilotStudioAgentAuthMode | Literal["interactive", "service"] | None = None,
+ auth_mode: CopilotStudioAgentAuthMode | Literal["interactive"] | None = None,
agent_identifier: str | None = None,
app_client_id: str | None = None,
- client_secret: str | None = None,
- client_certificate: str | None = None,
cloud: PowerPlatformCloud | None = None,
copilot_agent_type: AgentType | None = None,
custom_power_platform_cloud: str | None = None,
diff --git a/python/semantic_kernel/agents/copilot_studio/copilot_studio_agent.py b/python/semantic_kernel/agents/copilot_studio/copilot_studio_agent.py
index 4c8da0fb21bd..effeec5b4073 100644
--- a/python/semantic_kernel/agents/copilot_studio/copilot_studio_agent.py
+++ b/python/semantic_kernel/agents/copilot_studio/copilot_studio_agent.py
@@ -4,12 +4,11 @@
import sys
from collections.abc import AsyncIterable, Awaitable, Callable, Sequence
from os import environ, path
-from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Literal
from microsoft_agents.activity import ActivityTypes
from microsoft_agents.copilotstudio.client import AgentType, CopilotClient, PowerPlatformCloud
-from msal import ConfidentialClientApplication, PublicClientApplication
+from msal import PublicClientApplication
from msal_extensions import FilePersistence, PersistedTokenCache, build_encrypted_persistence
from pydantic import ValidationError
@@ -69,18 +68,10 @@ def __init__(
*,
settings: CopilotStudioAgentSettings,
cache_path: str,
- mode: CopilotStudioAgentAuthMode,
- client_secret: str | None = None,
- client_certificate: Path | None = None,
- user_assertion: str | None = None,
scopes: Sequence[str] | None = None,
) -> None:
self.settings = settings
self.cache = self._get_msal_token_cache(cache_path)
- self.mode = mode
- self.client_secret = client_secret
- self.client_cert_path = client_certificate
- self.user_assertion = user_assertion
self.scopes = scopes or ["https://api.powerplatform.com/.default"]
@staticmethod
@@ -105,52 +96,7 @@ def _get_msal_token_cache(cache_path: str, fallback_to_plaintext=True) -> Persis
def acquire(self) -> str:
"""Return a valid bearer token or raise AgentInitializationException."""
- if self.mode is CopilotStudioAgentAuthMode.SERVICE:
- # SERVICE auth wiring is present but not yet supported end-to-end.
- logger.warning("SERVICE authentication mode is not yet supported; falling back to error.")
- raise AgentInitializationException(
- "Copilot Studio SERVICE authentication is not available yet. Please use INTERACTIVE mode instead."
- )
-
- match self.mode:
- case CopilotStudioAgentAuthMode.SERVICE:
- return self._acquire_service_token() # unreachable until the guard is removed
- case _:
- return self._acquire_interactive_token()
-
- def _new_confidential_client(self, **extra_kwargs) -> ConfidentialClientApplication:
- return ConfidentialClientApplication(
- client_id=self.settings.app_client_id,
- authority=f"https://login.microsoftonline.com/{self.settings.tenant_id}",
- token_cache=self.cache,
- **extra_kwargs,
- )
-
- def _acquire_service_token(self) -> str:
- if not self.client_secret and not self.client_cert_path:
- raise AgentInitializationException(
- "client_secret *or* client_certificate is required for service-to-service auth."
- )
-
- kwargs: dict[str, Any] = {}
- if self.client_secret:
- kwargs["client_credential"] = self.client_secret
- else: # certificate
- if not self.client_cert_path:
- raise AgentInitializationException(
- "If no client_secret is provided, a client_certificate is required for service-to-service auth."
- )
- kwargs["client_credential"] = {
- "private_key": Path(self.client_cert_path).read_text(),
- "thumbprint": self._cert_thumbprint(self.client_cert_path),
- }
-
- app = self._new_confidential_client(**kwargs)
-
- # proactive caching
- result = app.acquire_token_silent(self.scopes, account=None) or app.acquire_token_for_client(scopes=self.scopes)
-
- return self._unwrap(result)
+ return self._acquire_interactive_token()
# interactive
def _acquire_interactive_token(self) -> str:
@@ -174,18 +120,6 @@ def _unwrap(result: dict[str, Any]) -> str:
_log_auth_failure(result)
raise AgentInitializationException("Authentication failed; see logs for category and correlation code.")
- @staticmethod
- def _cert_thumbprint(cert_path: Path) -> str:
- import hashlib
- import ssl
-
- pem_bytes = Path(cert_path).read_bytes()
- der_bytes = ssl.PEM_cert_to_DER_cert(pem_bytes.decode())
- # SHA-1 is not used here as a security primitive; it is required to compute the X.509 certificate thumbprint
- # (the `x5t` JWT header value), which MSAL and Microsoft Entra ID mandate to be a SHA-1 digest for the
- # `thumbprint` client credential. Hence the `usedforsecurity=False` flag.
- return hashlib.sha1(der_bytes, usedforsecurity=False).hexdigest().upper() # CodeQL [SM02167] x5t thumbprint
-
# endregion
@@ -329,11 +263,9 @@ def model_post_init(self, __ctx: Any) -> None:
@staticmethod
def create_client(
*,
- auth_mode: CopilotStudioAgentAuthMode | Literal["interactive", "service"] | None = None,
+ auth_mode: CopilotStudioAgentAuthMode | Literal["interactive"] | None = None,
agent_identifier: str | None = None,
app_client_id: str | None = None,
- client_secret: str | None = None,
- client_certificate: str | None = None,
cloud: PowerPlatformCloud | None = None,
copilot_agent_type: AgentType | None = None,
custom_power_platform_cloud: str | None = None,
@@ -346,12 +278,10 @@ def create_client(
"""Create the Copilot Studio Agent Client.
Args:
- auth_mode: The authentication mode. This can be either `interactive` or `service`.
+ auth_mode: The authentication mode. Only `interactive` is currently supported.
agent_identifier: The agent identifier. This is the `Schema Name` of the agent from the
Copilot Studio Advanced Metadata settings.
app_client_id: The app client ID. This is the app ID of the app registration configured in Entra.
- client_secret: The client secret. This is the secret of the app registration.
- client_certificate: The client certificate. This is the certificate of the app registration.
cloud: The cloud environment.
copilot_agent_type: The type of Copilot agent.
custom_power_platform_cloud: The custom Power Platform cloud.
@@ -378,8 +308,6 @@ def create_client(
custom_power_platform_cloud=custom_power_platform_cloud,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
- client_secret=client_secret,
- client_certificate=client_certificate,
user_assertion=user_assertion,
auth_mode=auth_mode,
)
@@ -397,12 +325,6 @@ def create_client(
token = _CopilotStudioAgentTokenFactory(
settings=connection_settings,
cache_path=cache_file,
- mode=connection_settings.auth_mode,
- client_secret=connection_settings.client_secret.get_secret_value()
- if connection_settings.client_secret
- else None,
- client_certificate=Path(client_certificate) if client_certificate else None,
- user_assertion=user_assertion,
).acquire()
return CopilotClient(connection_settings, token)
diff --git a/python/semantic_kernel/agents/copilot_studio/copilot_studio_agent_settings.py b/python/semantic_kernel/agents/copilot_studio/copilot_studio_agent_settings.py
index 797363345ac6..9c99d686210f 100644
--- a/python/semantic_kernel/agents/copilot_studio/copilot_studio_agent_settings.py
+++ b/python/semantic_kernel/agents/copilot_studio/copilot_studio_agent_settings.py
@@ -7,7 +7,7 @@
AgentType,
PowerPlatformCloud,
)
-from pydantic import Field, SecretStr
+from pydantic import Field
from semantic_kernel.kernel_pydantic import KernelBaseSettings
from semantic_kernel.utils.feature_stage_decorator import experimental
@@ -17,8 +17,7 @@
class CopilotStudioAgentAuthMode(str, Enum):
"""The Copilot Studio agent authentication mode."""
- INTERACTIVE = "interactive" # user authentication
- SERVICE = "service" # client-credentials (app secret/cert)
+ INTERACTIVE = "interactive"
@experimental
@@ -34,7 +33,5 @@ class CopilotStudioAgentSettings(KernelBaseSettings):
cloud: PowerPlatformCloud = Field(default=PowerPlatformCloud.UNKNOWN)
copilot_agent_type: AgentType = Field(default=AgentType.PUBLISHED)
custom_power_platform_cloud: str | None = None
- client_secret: SecretStr | None = None
- client_certificate: str | None = None
user_assertion: str | None = None
auth_mode: CopilotStudioAgentAuthMode = Field(default=CopilotStudioAgentAuthMode.INTERACTIVE)
diff --git a/python/tests/unit/agents/copilot_studio/test_copilot_studio_agent.py b/python/tests/unit/agents/copilot_studio/test_copilot_studio_agent.py
index fa0ce044ef70..3c3374e2f1bc 100644
--- a/python/tests/unit/agents/copilot_studio/test_copilot_studio_agent.py
+++ b/python/tests/unit/agents/copilot_studio/test_copilot_studio_agent.py
@@ -7,7 +7,7 @@
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
-from pydantic import SecretStr, ValidationError
+from pydantic import ValidationError
try:
import microsoft_agents.copilotstudio.client # type: ignore # noqa: F401
@@ -226,7 +226,6 @@ def test_setup_resources_happy_path(tmp_path, monkeypatch):
dummy_settings.app_client_id = "appid"
dummy_settings.tenant_id = "tenantid"
dummy_settings.auth_mode = CopilotStudioAgentAuthMode.INTERACTIVE
- dummy_settings.client_secret = SecretStr("test-secret")
monkeypatch.setattr(
"semantic_kernel.agents.copilot_studio.copilot_studio_agent.CopilotStudioAgentSettings",
@@ -246,7 +245,7 @@ def test_setup_resources_happy_path(tmp_path, monkeypatch):
tenant_id="tenantid",
environment_id="env-id",
agent_identifier="agent-name",
- auth_mode=CopilotStudioAgentAuthMode.SERVICE,
+ auth_mode=CopilotStudioAgentAuthMode.INTERACTIVE,
)
mock_client_ctor.assert_called_once_with(dummy_settings, "fake-token")
@@ -255,19 +254,6 @@ def test_setup_resources_happy_path(tmp_path, monkeypatch):
class DummyCache:
pass
- class FakeAppSilent:
- def __init__(self, client_id, authority, token_cache, client_credential=None, **kwargs):
- pass
-
- def get_accounts(self):
- return [{"home_account_id": "acct1"}]
-
- def acquire_token_silent(self, scopes, account):
- return {"access_token": "silent-token"}
-
- def acquire_token_interactive(self, scopes):
- pytest.skip("Unexpected interactive flow in silent test")
-
class FakeAppInteractive:
def __init__(self, client_id, authority, token_cache):
pass
@@ -306,54 +292,28 @@ def stub_cache(monkeypatch):
staticmethod(lambda cache_path, fallback_to_plaintext=True: DummyCache()),
)
- @pytest.mark.parametrize(
- "fake_app, expected_token, mode",
- [
- pytest.param(
- FakeAppSilent,
- "silent-token",
- CopilotStudioAgentAuthMode.SERVICE,
- marks=pytest.mark.skip(reason="Skipping SERVICE auth mode test as the mode is not yet supported."),
- ),
- (FakeAppInteractive, "interactive-token", CopilotStudioAgentAuthMode.INTERACTIVE),
- ],
- )
- def test_acquire_token_success(monkeypatch, tmp_path, fake_app, expected_token, mode):
+ def test_acquire_token_success(monkeypatch, tmp_path):
settings = CopilotStudioAgentSettings(app_client_id="id", tenant_id="tid")
cache_path = str(tmp_path / "cache.bin")
- monkeypatch.setattr(csa_mod, "PublicClientApplication", fake_app)
- monkeypatch.setattr(csa_mod, "ConfidentialClientApplication", fake_app)
-
- client_secret = None
- if fake_app == FakeAppSilent:
- client_secret = "test-secret"
+ monkeypatch.setattr(csa_mod, "PublicClientApplication", FakeAppInteractive)
factory = _CopilotStudioAgentTokenFactory(
settings=settings,
cache_path=cache_path,
- mode=mode,
- client_secret=client_secret,
- client_certificate=None,
- user_assertion=None,
)
token = factory.acquire()
- assert token == expected_token
+ assert token == "interactive-token"
def test_acquire_token_error(monkeypatch, tmp_path):
settings = CopilotStudioAgentSettings(app_client_id="id", tenant_id="tid")
cache_path = str(tmp_path / "cache.bin")
monkeypatch.setattr(csa_mod, "PublicClientApplication", FakeAppError)
- monkeypatch.setattr(csa_mod, "ConfidentialClientApplication", FakeAppError)
factory = _CopilotStudioAgentTokenFactory(
settings=settings,
cache_path=cache_path,
- mode=CopilotStudioAgentAuthMode.INTERACTIVE,
- client_secret=None,
- client_certificate=None,
- user_assertion=None,
)
with pytest.raises(AgentInitializationException):
factory.acquire()