Skip to content

Add Authentication Support for Entra Agent ID Container Side Car #411

@MattB-msft

Description

@MattB-msft

Entra ID Auth Sidecar Provider — Python Agents SDK

Summary

Add a new authentication provider package (microsoft-agents-authentication-entra-sidecar) to the Python Microsoft 365 Agents SDK that integrates with the Microsoft Entra ID Agent Container (sidecar). This provider implements the existing AccessTokenProviderBase protocol by delegating token acquisition to the sidecar's HTTP API rather than directly using the MSAL library.

Initial Scope: Acquire the base Blueprint Agentic Identity token from the sidecar container, then leverage the existing SDK resolution path for Agent Instance and Agent User tokens.


Motivation

The Entra ID Agent Container provides a language-agnostic, credential-free authentication sidecar that:

  1. Eliminates credential handling in agent code — The agent never touches secrets, certificates, or keys.
  2. Simplifies Python deployments — No MSAL dependency, no certificate file management, no asyncio.to_thread() wrapping of synchronous MSAL calls.
  3. Provides production-grade security — The sidecar supports Workload Identity, Managed Identity, Key Vault certificates, and federated credentials out of the box.
  4. Enables consistent local development — Docker Compose with the sidecar provides the same authentication experience locally as in production.

Scope

In Scope (Phase 1)

Capability Description
Blueprint Identity Acquisition Acquire the base Blueprint app token via GET /AuthorizationHeaderUnauthenticated/{name}
Agent Instance Resolution Pass the Blueprint token through the existing SDK path (get_agentic_instance_token)
Agent User Resolution Pass the Blueprint token through the existing SDK path (get_agentic_user_token)
AccessTokenProviderBase implementation Full implementation of get_access_token and get_agentic_application_token delegating to the sidecar
Connections protocol implementation SidecarConnectionManager implementing the connection registry
Configuration model New SidecarAuthConfiguration leveraging AgentAuthConfiguration patterns
Health check Validate sidecar availability via GET /healthz at startup

Out of Scope (Future Phases)

Capability Rationale
OBO token exchange via sidecar Requires inbound user token; Phase 2
Downstream API proxy (/DownstreamApi/* endpoints) SDK does not proxy API calls through auth providers
Token validation (/Validate endpoint) SDK handles inbound token validation separately
acquire_token_on_behalf_of via sidecar Deferred to Phase 2

Technical Design

Architecture

┌─────────────────────────────────────────────────────────┐
│  Agent Application (Python)                              │
│                                                          │
│  ┌──────────────────────────────────────────────────┐   │
│  │  Agents SDK                                       │   │
│  │                                                   │   │
│  │  AccessTokenProviderBase ◄── SidecarAuth          │   │
│  │         │                                         │   │
│  │         │ get_agentic_application_token()          │   │
│  │         ▼                                         │   │
│  │  ┌─────────────────────────┐                      │   │
│  │  │ SidecarTokenClient      │ ─── HTTP ───┐       │   │
│  │  └─────────────────────────┘              │       │   │
│  └───────────────────────────────────────────┼───────┘   │
│                                              │           │
└──────────────────────────────────────────────┼───────────┘
                                               │
                                               ▼
┌──────────────────────────────────────────────────────────┐
│  Entra ID Agent Container (Sidecar)         :5000        │
│                                                          │
│  GET /AuthorizationHeaderUnauthenticated/{name}          │
│      ?AgentIdentity={agent-client-id}                    │
│                                                          │
│  Response: { "authorizationHeader": "Bearer <token>" }   │
└──────────────────────────────────────────────────────────┘
                       │
                       ▼
          Microsoft Entra ID (login.microsoftonline.com)

Base Protocol (existing — from microsoft-agents-hosting-core)

class AccessTokenProviderBase(Protocol):

    @abstractmethod
    async def get_access_token(
        self, resource_url: str, scopes: list[str], force_refresh: bool = False
    ) -> str:
        pass

    async def acquire_token_on_behalf_of(
        self, scopes: list[str], user_assertion: str
    ) -> str:
        raise NotImplementedError()

    async def get_agentic_application_token(
        self, tenant_id: str, agent_app_instance_id: str
    ) -> Optional[str]:
        raise NotImplementedError()

    async def get_agentic_instance_token(
        self, tenant_id: str, agent_app_instance_id: str
    ) -> tuple[str, str]:
        raise NotImplementedError()

    async def get_agentic_user_token(
        self,
        tenant_id: str,
        agent_app_instance_id: str,
        agentic_user_id: str,
        scopes: list[str],
    ) -> Optional[str]:
        raise NotImplementedError()

SidecarAuth — Provider Implementation

import os
import logging
from typing import Optional
import httpx

from microsoft_agents.hosting.core import (
    AccessTokenProviderBase,
    AgentAuthConfiguration,
)

logger = logging.getLogger(__name__)


class SidecarAuth(AccessTokenProviderBase):
    """
    Authentication provider that delegates token acquisition to the
    Microsoft Entra ID Agent Container (sidecar).
    """

    def __init__(self, configuration: AgentAuthConfiguration):
        self._configuration = configuration
        # Resolution order: SIDECAR_URL env var > sidecar_base_url config > default
        self._sidecar_base_url = (
            os.environ.get("SIDECAR_URL")
            or getattr(configuration, "sidecar_base_url", None)
            or "http://localhost:5000"
        )
        self._http_client = httpx.AsyncClient(
            base_url=self._sidecar_base_url,
            timeout=httpx.Timeout(30.0),
        )

    async def get_access_token(
        self, resource_url: str, scopes: list[str], force_refresh: bool = False
    ) -> str:
        """
        Acquire an app-only access token from the sidecar.
        Called by SDK internals for service-to-service auth.
        """
        response = await self._http_client.get(
            "/AuthorizationHeaderUnauthenticated/default"
        )
        response.raise_for_status()
        return self._parse_token(response.json())

    async def get_agentic_application_token(
        self, tenant_id: str, agent_app_instance_id: str
    ) -> Optional[str]:
        """
        Acquire the Blueprint agentic identity token from the sidecar.
        The agent_app_instance_id is passed as the AgentIdentity query parameter.
        This value comes from the inbound ActivityProtocol request.
        """
        response = await self._http_client.get(
            "/AuthorizationHeaderUnauthenticated/default",
            params={"AgentIdentity": agent_app_instance_id},
        )
        response.raise_for_status()
        return self._parse_token(response.json())

    async def get_agentic_instance_token(
        self, tenant_id: str, agent_app_instance_id: str
    ) -> tuple[str, str]:
        """
        Acquire the Agent Instance token.
        Uses the Blueprint token from get_agentic_application_token,
        then follows the existing SDK path to derive the instance token.

        The agent_app_instance_id comes from the inbound ActivityProtocol request.
        """
        # Step 1: Get Blueprint token from sidecar
        agent_token = await self.get_agentic_application_token(
            tenant_id, agent_app_instance_id
        )
        if not agent_token:
            raise RuntimeError(
                f"Failed to acquire agentic application token for instance {agent_app_instance_id}"
            )

        # Step 2: Use existing SDK path — exchange Blueprint token for Instance token
        # The agent_token is used as client_assertion in a new CCA
        # with client_id = agent_app_instance_id
        instance_app = ConfidentialClientApplication(
            client_id=agent_app_instance_id,
            authority=f"https://login.microsoftonline.com/{tenant_id}",
            client_credential={"client_assertion": agent_token},
        )
        result = instance_app.acquire_token_for_client(
            scopes=["api://AzureAdTokenExchange/.default"]
        )
        if "access_token" not in result:
            raise RuntimeError(
                f"Failed to acquire agentic instance token: {result.get('error_description', 'unknown error')}"
            )
        return (result["access_token"], agent_token)

    async def get_agentic_user_token(
        self,
        tenant_id: str,
        agent_app_instance_id: str,
        agentic_user_id: str,
        scopes: list[str],
    ) -> Optional[str]:
        """
        Acquire a user-scoped token using the agentic identity chain.
        The agent_app_instance_id and agentic_user_id come from the
        inbound ActivityProtocol request.

        Uses the existing SDK path:
        1. Get instance_token + agent_token via get_agentic_instance_token
        2. Exchange for user token via user_fic grant
        """
        instance_token, agent_token = await self.get_agentic_instance_token(
            tenant_id, agent_app_instance_id
        )

        # Use agent_token as client_assertion, exchange for user token
        user_app = ConfidentialClientApplication(
            client_id=agent_app_instance_id,
            authority=f"https://login.microsoftonline.com/{tenant_id}",
            client_credential={"client_assertion": agent_token},
        )
        result = user_app.acquire_token_for_client(
            scopes=scopes,
            data={
                "user_id": agentic_user_id,
                "user_federated_identity_credential": instance_token,
                "grant_type": "user_fic",
            },
        )
        if "access_token" not in result:
            raise RuntimeError(
                f"Failed to acquire agentic user token: {result.get('error_description', 'unknown error')}"
            )
        return result["access_token"]

    async def is_healthy(self) -> bool:
        """Check sidecar availability via /healthz endpoint."""
        try:
            response = await self._http_client.get("/healthz")
            return response.status_code == 200
        except httpx.HTTPError:
            return False

    async def close(self):
        """Close the underlying HTTP client."""
        await self._http_client.aclose()

    @staticmethod
    def _parse_token(response_body: dict) -> str:
        """
        Extract raw access token from sidecar response.
        Response format: { "authorizationHeader": "Bearer eyJ..." }
        """
        auth_header = response_body.get("authorizationHeader", "")
        if auth_header.startswith("Bearer "):
            return auth_header[7:]
        return auth_header

Parameter Resolution from ActivityProtocol

The AgentIdentity (agent instance ID) and AgentUserId (agent object ID) values are not stored in configuration — they are extracted from the inbound ActivityProtocol request at runtime:

Parameter Source in ActivityProtocol Request Passed To
agent_app_instance_id The Agent Instance ID from the activity request get_agentic_application_token, get_agentic_instance_token, get_agentic_user_token — mapped to the AgentIdentity query parameter on the sidecar
agentic_user_id The Agent User object ID from the activity request get_agentic_user_token — mapped to the AgentUserId query parameter on the sidecar

This means the SDK resolves identity per-request based on the incoming activity, enabling a single agent deployment to serve multiple agent identities and users without reconfiguration.


Token Flow — Blueprint Identity (Phase 1 Focus)

Agent SDK (Python)                 Sidecar (:5000)                    Entra ID
   │                                    │                                │
   │ get_agentic_application_token()    │                                │
   │──────────────────────────────────►│                                │
   │  GET /AuthorizationHeaderUnauthenticated/default                    │
   │      ?AgentIdentity={agent_app_instance_id}                         │
   │                                    │                                │
   │                                    │ Client Credentials (Blueprint) │
   │                                    │───────────────────────────────►│
   │                                    │◄─────────── T1 (Blueprint) ────│
   │                                    │                                │
   │                                    │ FIC Exchange (AgentIdentity)   │
   │                                    │───────────────────────────────►│
   │                                    │◄─────────── TR (Agent App) ────│
   │                                    │                                │
   │◄─── { "authorizationHeader":       │                                │
   │       "Bearer TR" }                │                                │
   │                                    │                                │
   │ _parse_token() → strip "Bearer "   │                                │
   │ return TR                          │                                │
   │                                    │                                │
   │ ─── Existing SDK path ───          │                                │
   │ get_agentic_instance_token()       │                                │
   │   uses TR as client_assertion      │                                │
   │   in ConfidentialClientApplication │                                │
   │   to acquire Agent Instance token  │                                │
   │                                    │                                │
   │ get_agentic_user_token()           │                                │
   │   uses Instance token + user_fic   │                                │
   │   grant to acquire user token      │                                │

Key Insight: The sidecar provides the Blueprint application token (the first step in the agentic identity chain). The existing SDK logic for get_agentic_instance_token and get_agentic_user_token already knows how to use a Blueprint token to derive Instance and User tokens — this provider just changes how the initial Blueprint token is acquired.


Connection Manager — Refactoring to a Generic Provider-Agnostic Design

Rather than creating a sidecar-specific connection manager, the existing MsalConnectionManager should be refactored into a generic ConnectionManager that accepts any AccessTokenProviderBase implementation via a factory pattern. This enables support for additional auth providers in the future without duplicating the connection routing logic.

Rationale: The current MsalConnectionManager contains ~140 lines of connection routing logic (audience matching, service URL regex dispatch, default connection resolution) that is entirely provider-agnostic. The only MSAL-specific line is the instantiation of MsalAuth(config). Refactoring this into a generic base avoids duplicating routing logic for every new auth provider (Sidecar, future providers, etc.).

Proposed Generic Connection Manager

import re
from typing import Optional, Dict, List, Callable, Type
from microsoft_agents.hosting.core import (
    Connections,
    AccessTokenProviderBase,
    AgentAuthConfiguration,
    ClaimsIdentity,
)


class ConnectionManager(Connections):
    """
    Generic connection manager that dispatches to any AccessTokenProviderBase
    implementation. The provider_factory parameter determines which auth provider
    is instantiated for each connection configuration.

    This design supports adding new auth providers (Sidecar, MSAL, future providers)
    without duplicating connection routing logic.
    """

    def __init__(
        self,
        provider_factory: Callable[[AgentAuthConfiguration], AccessTokenProviderBase],
        connections_configurations: Optional[Dict[str, AgentAuthConfiguration]] = None,
        connections_map: Optional[List[Dict[str, str]]] = None,
        **kwargs,
    ):
        self._provider_factory = provider_factory
        self._connections: Dict[str, AccessTokenProviderBase] = {}
        self._connections_map = connections_map or kwargs.get("CONNECTIONSMAP", {})
        self._config_map: Dict[str, AgentAuthConfiguration] = {}

        if connections_configurations:
            for name, config in connections_configurations.items():
                self._connections[name] = provider_factory(config)
                self._config_map[name] = config
        else:
            raw_configurations: Dict[str, Dict] = kwargs.get("CONNECTIONS", {})
            for name, settings in raw_configurations.items():
                parsed_config = AgentAuthConfiguration(
                    **settings.get("SETTINGS", {})
                )
                self._connections[name] = provider_factory(parsed_config)
                self._config_map[name] = parsed_config

        # JWT-patch: share connections across configs for cross-connection validation
        for config in self._config_map.values():
            config._connections = self._config_map

        if "SERVICE_CONNECTION" not in self._connections:
            raise ValueError("No SERVICE_CONNECTION configuration provided.")

    def get_connection(self, connection_name: Optional[str]) -> AccessTokenProviderBase:
        connection_name = connection_name or "SERVICE_CONNECTION"
        connection = self._connections.get(connection_name)
        if not connection:
            raise ValueError(f"No connection found for '{connection_name}'.")
        return connection

    def get_default_connection(self) -> AccessTokenProviderBase:
        return self.get_connection("SERVICE_CONNECTION")

    def get_token_provider(
        self, claims_identity: ClaimsIdentity, service_url: str
    ) -> AccessTokenProviderBase:
        if not self._connections_map:
            return self.get_default_connection()

        aud = (claims_identity.get_app_id() or "").lower()
        for item in self._connections_map:
            item_aud = item.get("AUDIENCE", "").lower()
            if item_aud and item_aud != aud:
                continue
            item_svc = item.get("SERVICEURL", "*")
            if item_svc == "*" or not item_svc:
                return self.get_connection(item.get("CONNECTION"))
            if re.match(item_svc, service_url, re.IGNORECASE):
                return self.get_connection(item.get("CONNECTION"))

        return self.get_default_connection()

    def get_default_connection_configuration(self) -> AgentAuthConfiguration:
        config = self._config_map.get("SERVICE_CONNECTION")
        if not config:
            raise ValueError("No SERVICE_CONNECTION configuration found.")
        return config

Usage with Sidecar Provider

from microsoft_agents.hosting.core import ConnectionManager
from microsoft_agents.authentication.entra_sidecar import SidecarAuth

# The provider_factory is simply the SidecarAuth constructor
connection_manager = ConnectionManager(
    provider_factory=SidecarAuth,
    CONNECTIONS=agents_sdk_config["CONNECTIONS"],
    CONNECTIONSMAP=agents_sdk_config.get("CONNECTIONSMAP", []),
)

Backward Compatibility with MSAL

The existing MsalConnectionManager becomes a thin convenience wrapper:

from microsoft_agents.authentication.msal import MsalAuth

class MsalConnectionManager(ConnectionManager):
    """Convenience subclass that defaults to MsalAuth as the provider factory."""

    def __init__(self, connections_configurations=None, connections_map=None, **kwargs):
        super().__init__(
            provider_factory=MsalAuth,
            connections_configurations=connections_configurations,
            connections_map=connections_map,
            **kwargs,
        )

Future Extensibility

Adding a new auth provider requires only:

  1. Implementing a class that satisfies AccessTokenProviderBase
  2. Passing it as the provider_factory to ConnectionManager

No new connection manager class is needed.


Configuration

The provider supports an optional sidecar_base_url configuration field. If the SIDECAR_URL environment variable is also present, the environment variable takes precedence.

URL Resolution Precedence:

  1. SIDECAR_URL environment variable (highest priority)
  2. sidecar_base_url from configuration
  3. Default: http://localhost:5000

Environment variables:

Variable Required Default Description
SIDECAR_URL No http://localhost:5000 Sidecar HTTP endpoint (takes precedence over config setting)

AgentAuthConfiguration fields used:

Field Required Description
CLIENT_ID Yes Blueprint app registration client ID
SCOPES Yes OAuth scopes to request (e.g., ["api://.../.default"])
AUTH_TYPE No "EntraAuthSideCar" (new AuthTypes enum value). Defaults to "EntraAuthSideCar" when using SidecarAuth directly.
SIDECAR_BASE_URL No Optional sidecar HTTP endpoint. Overridden by SIDECAR_URL env var if both are set.

Note: No secrets, certificates, or tenant IDs are required in the agent's configuration. All credential management is handled by the sidecar container.

Environment variable style configuration (double-underscore pattern):

CONNECTIONS__SERVICE_CONNECTION__AUTHTYPE=EntraAuthSideCar
CONNECTIONS__SERVICE_CONNECTION__CLIENTID=<blueprint-app-id>
CONNECTIONS__SERVICE_CONNECTION__SCOPES__0=api://<blueprint-app-id>/.default
CONNECTIONS__SERVICE_CONNECTION__SIDECAR_BASE_URL=http://my-sidecar:5000  # optional config fallback
CONNECTIONS_MAP__0__SERVICEURL=*
CONNECTIONS_MAP__0__CONNECTION=SERVICE_CONNECTION
SIDECAR_URL=http://localhost:5000  # takes precedence over SIDECAR_BASE_URL if set

New AuthTypes Enum Value

class AuthTypes(str, Enum):
    certificate = "certificate"
    certificate_subject_name = "CertificateSubjectName"
    client_secret = "ClientSecret"
    user_managed_identity = "UserManagedIdentity"
    system_managed_identity = "SystemManagedIdentity"
    federated_credentials = "FederatedCredentials"
    entra_auth_sidecar = "EntraAuthSideCar"  # NEW — Entra ID Agent Container

Package Structure

Sidecar auth provider package (new):

libraries/microsoft-agents-authentication-entra-sidecar/
├── pyproject.toml
├── setup.py
├── microsoft_agents/
│   └── authentication/
│       └── entra_sidecar/
│           ├── __init__.py
│           ├── sidecar_auth.py
│           ├── sidecar_token_client.py
│           └── errors/
│               ├── __init__.py
│               └── error_resources.py

Generic ConnectionManager addition (in microsoft-agents-hosting-core):

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/
├── connection_manager.py   (NEW — generic provider-agnostic connection manager)
├── connections.py          (existing — Connections protocol)
└── ...

__init__.py exports (sidecar package):

from .sidecar_auth import SidecarAuth

__all__ = ["SidecarAuth"]

Note: No SidecarConnectionManager is needed. Use the generic ConnectionManager from microsoft-agents-hosting-core with provider_factory=SidecarAuth.

pyproject.toml:

[project]
name = "microsoft-agents-authentication-entra-sidecar"
requires-python = ">=3.10"

[project.dependencies]
microsoft-agents-hosting-core = "==<version>"
httpx = ">=0.27.0"

setup.py:

from setuptools import setup, find_namespace_packages

setup(
    name="microsoft-agents-authentication-entra-sidecar",
    packages=find_namespace_packages(include=["microsoft_agents.*"]),
    install_requires=[
        f"microsoft-agents-hosting-core=={package_version}",
        "httpx>=0.27.0",
    ],
    python_requires=">=3.10",
)

Error Handling

class SidecarAuthError(Exception):
    """Base exception for sidecar authentication errors."""
    pass

class SidecarUnavailableError(SidecarAuthError):
    """Raised when the sidecar is unreachable."""
    pass

class SidecarConfigurationError(SidecarAuthError):
    """Raised when the sidecar returns 404 (misconfigured resource name)."""
    pass
Sidecar Response SDK Behavior
200 OK + valid JSON Parse authorizationHeader, strip "Bearer " prefix, return token
401 Unauthorized Raise SidecarAuthError — sidecar credentials misconfigured
404 Not Found Raise SidecarConfigurationError — resource not configured on sidecar
400 Bad Request Raise ValueError with sidecar error body (e.g., missing AgentIdentity)
500 Internal Server Error Retry per policy; raise SidecarAuthError after exhausting retries
Connection refused / timeout Retry per policy; raise SidecarUnavailableError
Non-JSON response Raise SidecarAuthError with raw response for diagnostics

Retry and Resilience

import httpx
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type

RETRY_DECORATOR = retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=1, max=10),
    retry=retry_if_exception_type((httpx.ConnectError, httpx.TimeoutException)),
)

Alternatively, use httpx transport-level retries or keep retry logic minimal for Phase 1 and rely on the sidecar's own resilience.


Token Caching Strategy

Concern Strategy
Sidecar-side caching The sidecar itself caches tokens internally; repeated calls may return cached tokens
SDK-side caching Use the existing SDK token expiration management process (same as MSAL provider)
force_refresh support Bypass SDK cache; the sidecar will still use its internal cache unless expired

Security Considerations

  1. Network isolation — The sidecar MUST only be accessible from the agent container (same pod in K8s, same Docker bridge network). The SDK MUST validate that the resolved Entra Auth SideCar Base URL points to a loopback address (e.g., localhost, 127.0.0.1, [::1]) or a private network address (RFC 1918 / RFC 4193). If the resolved URL is a non-loopback, non-private address, the provider MUST raise an error and refuse to issue requests. Exception: This validation MUST be skipped if the sidecar_base_url is explicitly set in the configuration section of the EntraAuthSideCar entry — an explicit configuration value signals intentional operator override (e.g., for sidecar deployed as a separate container with a routable address within a private network).

  2. No credentials in agent config — Unlike the MSAL provider, this provider does NOT require secrets, certificates, or credential configuration.

  3. Token in transit — Communication between SDK and sidecar is over HTTP (not HTTPS) within the pod boundary. This is acceptable because traffic never leaves the pod/node network namespace.

  4. Response validation — Validate that authorizationHeader is non-empty and starts with Bearer before stripping the prefix.


Usage Example

from microsoft_agents.authentication.entra_sidecar import (
    SidecarAuth,
    SidecarConnectionManager,
)
from microsoft_agents.hosting.core import AgentAuthConfiguration

# Configuration — no secrets needed
config = AgentAuthConfiguration(
    auth_type="EntraAuthSideCar",
    client_id="<blueprint-app-id>",
)

# Create connection manager (SDK wiring)
manager = SidecarConnectionManager(
    connections_configurations={
        "SERVICE_CONNECTION": config,
    }
)

# Or via dict-based config (YAML-loaded)
manager = SidecarConnectionManager(
    CONNECTIONS={
        "SERVICE_CONNECTION": {
            "SETTINGS": {
                "AUTHTYPE": "EntraAuthSideCar",
                "CLIENTID": "<blueprint-app-id>",
            }
        }
    }
)

# The SDK automatically calls get_agentic_application_token
# with agent_app_instance_id from the inbound ActivityProtocol request

Testing Strategy

Test Type Description
Unit tests Mock httpx.AsyncClient to simulate sidecar responses (200, 401, 404, 500, timeout)
Integration tests Docker Compose with actual sidecar container + test Entra tenant
Health check tests Verify startup behavior when sidecar is unavailable
E2E tests Full agent scenario: SDK → Sidecar → Entra ID → downstream API

Open Questions

# Question Impact
1 Should the SDK-side cache read the JWT exp claim, or rely solely on a configured TTL? Resolved: Use the existing SDK token expiration management process (same as MSAL provider).
2 Should get_agentic_instance_token and get_agentic_user_token also delegate to the sidecar (future), or always use the existing SDK path after getting the Blueprint token? Resolved: Use the existing SDK built-in system. Can be revisited in a future phase.
3 What is the startup behavior if /healthz fails? Fail-fast (raise) or deferred-failure (fail on first token request)? Developer experience
4 Should httpx or aiohttp be the HTTP client? (httpx recommended for async-first design and consistent API) Dependency choice
5 Does the sidecar return token expiry metadata or should the SDK always parse the JWT? Token caching implementation

References

Metadata

Metadata

Assignees

Labels

No labels
No labels
No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions