SDK Sidecar — Code Patterns, Deployment, and Security
Companion to sdk-sidecar.md. This file covers calling-side code patterns in Python and TypeScript, container deployment manifests, security hardening, and troubleshooting.
Code Patterns
Autonomous 3P Agent (Python)
import os
import requests
SIDECAR_URL = os.environ.get("SIDECAR_URL", "http://localhost:5000")
AGENT_APP_ID = os.environ["AGENT_CLIENT_ID"]
def get_agent_token(downstream_api: str = "Graph") -> str:
url = f"{SIDECAR_URL}/AuthorizationHeaderUnauthenticated/{downstream_api}"
resp = requests.get(url, params={"AgentIdentity": AGENT_APP_ID}, timeout=30)
resp.raise_for_status()
return resp.json()["authorizationHeader"]
def call_downstream_api(endpoint: str) -> dict:
token = get_agent_token()
resp = requests.get(endpoint, headers={"Authorization": token}, timeout=10)
resp.raise_for_status()
return resp.json()Interactive (OBO) Agent (Python)
def get_delegated_token(user_token: str, downstream_api: str = "Graph") -> str:
url = f"{SIDECAR_URL}/AuthorizationHeader/{downstream_api}"
resp = requests.get(
url,
headers={"Authorization": f"Bearer {user_token}"},
timeout=30,
)
resp.raise_for_status()
return resp.json()["authorizationHeader"]Autonomous Agent (TypeScript)
const SIDECAR_URL = process.env.SIDECAR_URL ?? "http://localhost:5000";
const AGENT_APP_ID = process.env.AGENT_CLIENT_ID!;
async function getAgentToken(downstreamApi = "Graph"): Promise<string> {
const url = new URL(
`/AuthorizationHeaderUnauthenticated/${downstreamApi}`,
SIDECAR_URL,
);
url.searchParams.set("AgentIdentity", AGENT_APP_ID);
const res = await fetch(url.toString());
if (!res.ok) {
const errorText = await res.text().catch(() => "");
throw new Error(
`SDK error: ${res.status}${errorText ? ` - ${errorText}` : ""}`,
);
}
const data = await res.json();
return data.authorizationHeader;
}Token Validation Middleware
def validate_incoming_token(bearer_token: str) -> dict:
resp = requests.get(
f"{SIDECAR_URL}/Validate",
headers={"Authorization": f"Bearer {bearer_token}"},
timeout=10,
)
resp.raise_for_status()
return resp.json()["claims"]Direct Downstream Call via SDK
def call_graph_me(user_token: str) -> dict:
resp = requests.get(
f"{SIDECAR_URL}/DownstreamApi/Graph",
params={"optionsOverride.RelativePath": "me"},
headers={"Authorization": f"Bearer {user_token}"},
timeout=10,
)
resp.raise_for_status()
return resp.json()["content"]Deployment
Docker Compose (Development)
version: '3.8'
services:
sidecar:
image: mcr.microsoft.com/entra-sdk/auth-sidecar:1.0.0-azurelinux3.0-distroless
ports:
- "5001:5000"
environment:
- AzureAd__Instance=https://login.microsoftonline.com/
- AzureAd__TenantId=${TENANT_ID}
- AzureAd__ClientId=${BLUEPRINT_APP_ID}
- AzureAd__ClientCredentials__0__SourceType=ClientSecret
- AzureAd__ClientCredentials__0__ClientSecret=${BLUEPRINT_CLIENT_SECRET}
- DownstreamApis__Graph__BaseUrl=https://graph.microsoft.com/v1.0/
- DownstreamApis__Graph__Scopes__0=https://graph.microsoft.com/.default
- DownstreamApis__Graph__RequestAppToken=true
- ASPNETCORE_URLS=http://+:5000
agent:
build: ./agent
ports:
- "3000:3000"
environment:
- SIDECAR_URL=http://sidecar:5000
- AGENT_CLIENT_ID=${AGENT_CLIENT_ID}
depends_on:
- sidecarKubernetes (Production)
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: agent
azure.workload.identity/use: "true"
spec:
serviceAccountName: agent-sa
containers:
- name: agent
image: myregistry/agent:latest
env:
- name: SIDECAR_URL
value: "http://localhost:5000"
- name: sidecar
image: mcr.microsoft.com/entra-sdk/auth-sidecar:1.0.0-azurelinux3.0-distroless
ports:
- containerPort: 5000
env:
- name: AzureAd__TenantId
valueFrom:
configMapKeyRef:
name: agent-config
key: tenant-id
- name: AzureAd__ClientId
valueFrom:
configMapKeyRef:
name: agent-config
key: client-id
- name: AzureAd__ClientCredentials__0__SourceType
value: "SignedAssertionFilePath"
- name: Kestrel__Endpoints__Http__Url
value: "http://127.0.0.1:5000"
resources:
requests: { memory: "128Mi", cpu: "100m" }
limits: { memory: "256Mi", cpu: "250m" }
livenessProbe:
httpGet: { path: /healthz, port: 5000 }
initialDelaySeconds: 10
readinessProbe:
httpGet: { path: /healthz, port: 5000 }
initialDelaySeconds: 5Security
⚠️ The SDK API must NOT be publicly accessible. Pod-local or same-Docker-network only.
- Bind to localhost —
Kestrel__Endpoints__Http__Url=http://127.0.0.1:5000 - Never expose via LoadBalancer or Ingress
- Use Workload Identity in AKS —
SignedAssertionFilePathover client secrets - Use Key Vault for certificates —
SourceType=KeyVaultin production - Separate ConfigMap from Secrets in Kubernetes
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
404 on /AuthorizationHeader/{name} |
{name} not in config |
Add DownstreamApis__{name}__BaseUrl env var |
400 AgentUsername requires AgentIdentity |
Missing AgentIdentity param |
Always pair user params with AgentIdentity |
400 mutually exclusive |
Both AgentUsername and AgentUserId |
Use one or the other |
401 on /Validate |
Invalid/expired inbound token | Check token audience matches AzureAd__ClientId |
| 500 token acquisition failure | Wrong creds or missing admin consent | kubectl logs <pod> -c sidecar |
| Connection refused | SDK not ready or wrong URL | Verify SIDECAR_URL and /healthz |