Skip to content

Commit 19d8cea

Browse files
tonybaloneyCopilot
andauthored
docs: Add Azure Managed Identity guide for BYOK (#498)
* docs: Add Azure Managed Identity guide for BYOK Add a guide showing how to use DefaultAzureCredential with the Copilot SDK's BYOK mode to authenticate against Azure AI Foundry using Managed Identity instead of static API keys. The pattern obtains a short-lived bearer token from Entra ID and injects it as the api_key in the ProviderConfig. Includes examples for Python, Node.js/TypeScript, and .NET, plus a token-refresh pattern for long-running applications. Also updates the BYOK limitations table to reference this workaround and adds the guide to the setup index decision matrix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback: use bearer_token, fix relative links - Use dedicated bearer_token/bearerToken/BearerToken provider config field instead of api_key for Entra ID tokens (all 3 language examples) - Fix relative links: ../guides/setup/ → ./ (same directory) - Add docs-validate: skip for TypeScript and C# blocks that depend on @azure/identity and Azure.Identity packages not in the validation project - Update prose to reference bearer_token instead of api_key Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8a9f992 commit 19d8cea

File tree

3 files changed

+219
-1
lines changed

3 files changed

+219
-1
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# Azure Managed Identity with BYOK
2+
3+
The Copilot SDK's [BYOK mode](./byok.md) accepts static API keys, but Azure deployments often use **Managed Identity** (Entra ID) instead of long-lived keys. Since the SDK doesn't natively support Entra ID authentication, you can use a short-lived bearer token via the `bearer_token` provider config field.
4+
5+
This guide shows how to use `DefaultAzureCredential` from the [Azure Identity](https://learn.microsoft.com/python/api/azure-identity/azure.identity.defaultazurecredential) library to authenticate with Azure AI Foundry models through the Copilot SDK.
6+
7+
## How It Works
8+
9+
Azure AI Foundry's OpenAI-compatible endpoint accepts bearer tokens from Entra ID in place of static API keys. The pattern is:
10+
11+
1. Use `DefaultAzureCredential` to obtain a token for the `https://cognitiveservices.azure.com/.default` scope
12+
2. Pass the token as the `bearer_token` in the BYOK provider config
13+
3. Refresh the token before it expires (tokens are typically valid for ~1 hour)
14+
15+
```mermaid
16+
sequenceDiagram
17+
participant App as Your Application
18+
participant AAD as Entra ID
19+
participant SDK as Copilot SDK
20+
participant Foundry as Azure AI Foundry
21+
22+
App->>AAD: DefaultAzureCredential.get_token()
23+
AAD-->>App: Bearer token (~1hr)
24+
App->>SDK: create_session(provider={bearer_token: token})
25+
SDK->>Foundry: Request with Authorization: Bearer <token>
26+
Foundry-->>SDK: Model response
27+
SDK-->>App: Session events
28+
```
29+
30+
## Python Example
31+
32+
### Prerequisites
33+
34+
```bash
35+
pip install github-copilot-sdk azure-identity
36+
```
37+
38+
### Basic Usage
39+
40+
```python
41+
import asyncio
42+
import os
43+
44+
from azure.identity import DefaultAzureCredential
45+
from copilot import CopilotClient, ProviderConfig, SessionConfig
46+
47+
COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default"
48+
49+
50+
async def main():
51+
# Get a token using Managed Identity, Azure CLI, or other credential chain
52+
credential = DefaultAzureCredential()
53+
token = credential.get_token(COGNITIVE_SERVICES_SCOPE).token
54+
55+
foundry_url = os.environ["AZURE_AI_FOUNDRY_RESOURCE_URL"]
56+
57+
client = CopilotClient()
58+
await client.start()
59+
60+
session = await client.create_session(
61+
SessionConfig(
62+
model="gpt-4.1",
63+
provider=ProviderConfig(
64+
type="openai",
65+
base_url=f"{foundry_url.rstrip('/')}/openai/v1/",
66+
bearer_token=token, # Short-lived bearer token
67+
wire_api="responses",
68+
),
69+
)
70+
)
71+
72+
response = await session.send_and_wait({"prompt": "Hello from Managed Identity!"})
73+
print(response.data.content)
74+
75+
await client.stop()
76+
77+
78+
asyncio.run(main())
79+
```
80+
81+
### Token Refresh for Long-Running Applications
82+
83+
Bearer tokens expire (typically after ~1 hour). For servers or long-running agents, refresh the token before creating each session:
84+
85+
```python
86+
from azure.identity import DefaultAzureCredential
87+
from copilot import CopilotClient, ProviderConfig, SessionConfig
88+
89+
COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default"
90+
91+
92+
class ManagedIdentityCopilotAgent:
93+
"""Copilot agent that refreshes Entra ID tokens for Azure AI Foundry."""
94+
95+
def __init__(self, foundry_url: str, model: str = "gpt-4.1"):
96+
self.foundry_url = foundry_url.rstrip("/")
97+
self.model = model
98+
self.credential = DefaultAzureCredential()
99+
self.client = CopilotClient()
100+
101+
def _get_session_config(self) -> SessionConfig:
102+
"""Build a SessionConfig with a fresh bearer token."""
103+
token = self.credential.get_token(COGNITIVE_SERVICES_SCOPE).token
104+
return SessionConfig(
105+
model=self.model,
106+
provider=ProviderConfig(
107+
type="openai",
108+
base_url=f"{self.foundry_url}/openai/v1/",
109+
bearer_token=token,
110+
wire_api="responses",
111+
),
112+
)
113+
114+
async def chat(self, prompt: str) -> str:
115+
"""Send a prompt and return the response text."""
116+
# Fresh token for each session
117+
config = self._get_session_config()
118+
session = await self.client.create_session(config)
119+
120+
response = await session.send_and_wait({"prompt": prompt})
121+
await session.destroy()
122+
123+
return response.data.content if response else ""
124+
```
125+
126+
## Node.js / TypeScript Example
127+
128+
<!-- docs-validate: skip -->
129+
```typescript
130+
import { DefaultAzureCredential } from "@azure/identity";
131+
import { CopilotClient } from "@github/copilot-sdk";
132+
133+
const credential = new DefaultAzureCredential();
134+
const tokenResponse = await credential.getToken(
135+
"https://cognitiveservices.azure.com/.default"
136+
);
137+
138+
const client = new CopilotClient();
139+
140+
const session = await client.createSession({
141+
model: "gpt-4.1",
142+
provider: {
143+
type: "openai",
144+
baseUrl: `${process.env.AZURE_AI_FOUNDRY_RESOURCE_URL}/openai/v1/`,
145+
bearerToken: tokenResponse.token,
146+
wireApi: "responses",
147+
},
148+
});
149+
150+
const response = await session.sendAndWait({ prompt: "Hello!" });
151+
console.log(response?.data.content);
152+
153+
await client.stop();
154+
```
155+
156+
## .NET Example
157+
158+
<!-- docs-validate: skip -->
159+
```csharp
160+
using Azure.Identity;
161+
using GitHub.Copilot;
162+
163+
var credential = new DefaultAzureCredential();
164+
var token = await credential.GetTokenAsync(
165+
new Azure.Core.TokenRequestContext(
166+
new[] { "https://cognitiveservices.azure.com/.default" }));
167+
168+
await using var client = new CopilotClient();
169+
var foundryUrl = Environment.GetEnvironmentVariable("AZURE_AI_FOUNDRY_RESOURCE_URL");
170+
171+
await using var session = await client.CreateSessionAsync(new SessionConfig
172+
{
173+
Model = "gpt-4.1",
174+
Provider = new ProviderConfig
175+
{
176+
Type = "openai",
177+
BaseUrl = $"{foundryUrl!.TrimEnd('/')}/openai/v1/",
178+
BearerToken = token.Token,
179+
WireApi = "responses",
180+
},
181+
});
182+
183+
var response = await session.SendAndWaitAsync(
184+
new MessageOptions { Prompt = "Hello from Managed Identity!" });
185+
Console.WriteLine(response?.Data.Content);
186+
```
187+
188+
## Environment Configuration
189+
190+
| Variable | Description | Example |
191+
|----------|-------------|---------|
192+
| `AZURE_AI_FOUNDRY_RESOURCE_URL` | Your Azure AI Foundry resource URL | `https://myresource.openai.azure.com` |
193+
194+
No API key environment variable is needed — authentication is handled by `DefaultAzureCredential`, which automatically supports:
195+
196+
- **Managed Identity** (system-assigned or user-assigned) — for Azure-hosted apps
197+
- **Azure CLI** (`az login`) — for local development
198+
- **Environment variables** (`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`) — for service principals
199+
- **Workload Identity** — for Kubernetes
200+
201+
See the [DefaultAzureCredential documentation](https://learn.microsoft.com/python/api/azure-identity/azure.identity.defaultazurecredential) for the full credential chain.
202+
203+
## When to Use This Pattern
204+
205+
| Scenario | Recommendation |
206+
|----------|----------------|
207+
| Azure-hosted app with Managed Identity | ✅ Use this pattern |
208+
| App with existing Azure AD service principal | ✅ Use this pattern |
209+
| Local development with `az login` | ✅ Use this pattern |
210+
| Non-Azure environment with static API key | Use [standard BYOK](./byok.md) |
211+
| GitHub Copilot subscription available | Use [GitHub OAuth](./github-oauth.md) |
212+
213+
## See Also
214+
215+
- [BYOK Setup Guide](./byok.md) — Static API key configuration
216+
- [Backend Services](./backend-services.md) — Server-side deployment
217+
- [Azure Identity documentation](https://learn.microsoft.com/python/api/overview/azure/identity-readme)

docs/guides/setup/byok.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ const resumed = await client.resumeSession("task-123", {
337337

338338
| Limitation | Details |
339339
|------------|---------|
340-
| **Static credentials only** | API keys or bearer tokens — no Entra ID, OIDC, or managed identities |
340+
| **Static credentials only** | API keys or bearer tokens — no native Entra ID, OIDC, or managed identity support. See [Azure Managed Identity workaround](./azure-managed-identity.md) for using `DefaultAzureCredential` with short-lived tokens. |
341341
| **No auto-refresh** | If a bearer token expires, you must create a new session |
342342
| **Your billing** | All model usage is billed to your provider account |
343343
| **Model availability** | Limited to what your provider offers |

docs/guides/setup/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ Use this table to find the right guides based on what you need to do:
8686
| Ship a standalone app with Copilot | [Bundled CLI](./bundled-cli.md) |
8787
| Users sign in with GitHub | [GitHub OAuth](./github-oauth.md) |
8888
| Use your own model keys (OpenAI, Azure, etc.) | [BYOK](./byok.md) |
89+
| Azure BYOK with Managed Identity (no API keys) | [Azure Managed Identity](./azure-managed-identity.md) |
8990
| Run the SDK on a server | [Backend Services](./backend-services.md) |
9091
| Serve multiple users / scale horizontally | [Scaling & Multi-Tenancy](./scaling.md) |
9192

0 commit comments

Comments
 (0)