Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 106 additions & 58 deletions docs/setup/azure-managed-identity.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,97 @@
# Azure managed identity with BYOK

The Copilot SDK's [BYOK mode](../auth/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.
The Copilot SDK's [BYOK mode](../auth/byok.md) accepts static API keys, but Azure deployments often use **Managed Identity** (Microsoft Entra ID) instead of long-lived keys. Since the SDK doesn't natively support Microsoft Entra authentication, you can use a short-lived bearer token via the `bearer_token` provider config field.

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.
This guide shows how to use the Azure Identity SDK's `DefaultAzureCredential` API to authenticate with Microsoft Foundry models through the Copilot SDK.

## How it works

Azure AI Foundry's OpenAI-compatible endpoint accepts bearer tokens from Entra ID in place of static API keys. The pattern is:
Microsoft Foundry's OpenAI-compatible endpoint accepts bearer tokens from Microsoft Entra ID in place of static API keys. The pattern is:

1. Use `DefaultAzureCredential` to obtain a token for the `https://cognitiveservices.azure.com/.default` scope
1. Use `DefaultAzureCredential` to obtain a token for the `https://ai.azure.com/.default` scope
1. Pass the token as the `bearer_token` in the BYOK provider config
1. Refresh the token before it expires (tokens are typically valid for ~1 hour)

```mermaid
sequenceDiagram
participant App as Your Application
participant AAD as Entra ID
participant MEID as Microsoft Entra ID
participant SDK as Copilot SDK
participant Foundry as Azure AI Foundry
participant Foundry as Microsoft Foundry

App->>AAD: DefaultAzureCredential.get_token()
AAD-->>App: Bearer token (~1hr)
App->>MEID: DefaultAzureCredential.get_token()
MEID-->>App: Bearer token (~1hr)
App->>SDK: create_session(provider={bearer_token: token})
SDK->>Foundry: Request with Authorization: Bearer <token>
Foundry-->>SDK: Model response
SDK-->>App: Session events
```

## Python example
## Code samples

<details open>
<summary><strong>.NET</strong></summary>

### Prerequisites

Install the required packages:

```bash
dotnet add package GitHub.Copilot.SDK
dotnet add package Azure.Core
Comment thread
scottaddie marked this conversation as resolved.
```

### Basic usage

<!-- docs-validate: skip -->

```csharp
using Azure.Core;
using Azure.Identity;
using GitHub.Copilot;

DefaultAzureCredential credential = new(
DefaultAzureCredential.DefaultEnvironmentVariableName);
AccessToken token = await credential.GetTokenAsync(
new TokenRequestContext(new[] { "https://ai.azure.com/.default" }));
Comment thread
scottaddie marked this conversation as resolved.

await using CopilotClient client = new();
string? foundryUrl = Environment.GetEnvironmentVariable("FOUNDRY_RESOURCE_URL");

await using CopilotSession session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-5.5",
Provider = new ProviderConfig
{
Type = "openai",
BaseUrl = $"{foundryUrl!.TrimEnd('/')}/openai/v1/",
BearerToken = token.Token,
WireApi = "responses",
},
});

AssistantMessageEvent? response = await session.SendAndWaitAsync(
new MessageOptions { Prompt = "Hello from Managed Identity!" });
Console.WriteLine(response?.Data.Content);
```

</details>

<details>
<summary><strong>Python</strong></summary>

### Prerequisites

Install the required packages:

```bash
pip install github-copilot-sdk azure-identity
```

### Basic usage

<!-- docs-validate: skip -->

```python
import asyncio
import os
Expand All @@ -45,22 +100,22 @@ from azure.identity import DefaultAzureCredential
from copilot import CopilotClient
from copilot.session import PermissionHandler, ProviderConfig

COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default"
SCOPE = "https://ai.azure.com/.default"


async def main():
# Get a token using Managed Identity, Azure CLI, or other credential chain
credential = DefaultAzureCredential()
token = credential.get_token(COGNITIVE_SERVICES_SCOPE).token
credential = DefaultAzureCredential(require_envvar=True)
token = credential.get_token(SCOPE).token
Comment thread
scottaddie marked this conversation as resolved.

foundry_url = os.environ["AZURE_AI_FOUNDRY_RESOURCE_URL"]
foundry_url = os.environ["FOUNDRY_RESOURCE_URL"]

client = CopilotClient()
await client.start()

session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
model="gpt-4.1",
model="gpt-5.5",
provider=ProviderConfig(
type="openai",
base_url=f"{foundry_url.rstrip('/')}/openai/v1/",
Expand All @@ -82,26 +137,28 @@ asyncio.run(main())

Bearer tokens expire (typically after ~1 hour). For servers or long-running agents, refresh the token before creating each session:

<!-- docs-validate: skip -->

```python
from azure.identity import DefaultAzureCredential
from copilot import CopilotClient
from copilot.session import PermissionHandler, ProviderConfig

COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default"
SCOPE = "https://ai.azure.com/.default"


class ManagedIdentityCopilotAgent:
"""Copilot agent that refreshes Entra ID tokens for Azure AI Foundry."""
"""Copilot agent that refreshes Microsoft Entra tokens for Microsoft Foundry."""

def __init__(self, foundry_url: str, model: str = "gpt-4.1"):
def __init__(self, foundry_url: str, model: str = "gpt-5.5"):
self.foundry_url = foundry_url.rstrip("/")
self.model = model
self.credential = DefaultAzureCredential()
self.credential = DefaultAzureCredential(require_envvar=True)
Comment thread
scottaddie marked this conversation as resolved.
self.client = CopilotClient()

def _get_provider_config(self) -> ProviderConfig:
"""Build a ProviderConfig with a fresh bearer token."""
token = self.credential.get_token(COGNITIVE_SERVICES_SCOPE).token
token = self.credential.get_token(SCOPE).token
return ProviderConfig(
type="openai",
base_url=f"{self.foundry_url}/openai/v1/",
Expand All @@ -124,25 +181,41 @@ class ManagedIdentityCopilotAgent:
return response.data.content if response else ""
```

## Node.js / TypeScript example
</details>

<details>
<summary><strong>TypeScript</strong></summary>

### Prerequisites

Install the required packages:

```bash
npm install @github/copilot-sdk @azure/identity
```

### Basic usage

<!-- docs-validate: skip -->

```typescript
import { DefaultAzureCredential } from "@azure/identity";
import { CopilotClient } from "@github/copilot-sdk";

const credential = new DefaultAzureCredential();
const credential = new DefaultAzureCredential({
requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"],
});
const tokenResponse = await credential.getToken(
"https://cognitiveservices.azure.com/.default"
"https://ai.azure.com/.default"
);
Comment thread
scottaddie marked this conversation as resolved.

const client = new CopilotClient();

const session = await client.createSession({
model: "gpt-4.1",
model: "gpt-5.5",
provider: {
type: "openai",
baseUrl: `${process.env.AZURE_AI_FOUNDRY_RESOURCE_URL}/openai/v1/`,
baseUrl: `${process.env.FOUNDRY_RESOURCE_URL}/openai/v1/`,
bearerToken: tokenResponse.token,
wireApi: "responses",
},
Expand All @@ -154,43 +227,14 @@ console.log(response?.data.content);
await client.stop();
```

## .NET example

<!-- docs-validate: skip -->
```csharp
using Azure.Identity;
using GitHub.Copilot;

var credential = new DefaultAzureCredential();
var token = await credential.GetTokenAsync(
new Azure.Core.TokenRequestContext(
new[] { "https://cognitiveservices.azure.com/.default" }));

await using var client = new CopilotClient();
var foundryUrl = Environment.GetEnvironmentVariable("AZURE_AI_FOUNDRY_RESOURCE_URL");

await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
Provider = new ProviderConfig
{
Type = "openai",
BaseUrl = $"{foundryUrl!.TrimEnd('/')}/openai/v1/",
BearerToken = token.Token,
WireApi = "responses",
},
});

var response = await session.SendAndWaitAsync(
new MessageOptions { Prompt = "Hello from Managed Identity!" });
Console.WriteLine(response?.Data.Content);
```
</details>

## Environment configuration

| Variable | Description | Example |
|----------|-------------|---------|
| `AZURE_AI_FOUNDRY_RESOURCE_URL` | Your Azure AI Foundry resource URL | `https://myresource.openai.azure.com` |
| `AZURE_TOKEN_CREDENTIALS` | When running in **Azure**, set it to `ManagedIdentityCredential`. When running **locally**, set it to either `dev` or a developer tool credential name, such as `AzureCliCredential`. | |
Comment thread
scottaddie marked this conversation as resolved.
| `FOUNDRY_RESOURCE_URL` | Your Microsoft Foundry resource URL | `https://<my-resource>.openai.azure.com` |

No API key environment variable is needed—authentication is handled by `DefaultAzureCredential`, which automatically supports:

Expand All @@ -199,14 +243,18 @@ No API key environment variable is needed—authentication is handled by `Defaul
* **Environment variables** (`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`): for service principals
* **Workload Identity**: for Kubernetes

See the [DefaultAzureCredential documentation](https://learn.microsoft.com/python/api/azure-identity/azure.identity.defaultazurecredential) for the full credential chain.
See the `DefaultAzureCredential` documentation for the full credential chain:

* [.NET](https://aka.ms/azsdk/net/identity/credential-chains#defaultazurecredential-overview)
* [Python](https://aka.ms/azsdk/python/identity/credential-chains#defaultazurecredential-overview)
* [TypeScript](https://aka.ms/azsdk/js/identity/credential-chains#defaultazurecredential-overview)

## When to use this pattern

| Scenario | Recommendation |
|----------|----------------|
| Azure-hosted app with Managed Identity | ✅ Use this pattern |
| App with existing Azure AD service principal | ✅ Use this pattern |
| App with existing Microsoft Entra service principal | ✅ Use this pattern |
| Local development with `az login` | ✅ Use this pattern |
| Non-Azure environment with static API key | Use [standard BYOK](../auth/byok.md) |
| GitHub Copilot subscription available | Use [GitHub OAuth](./github-oauth.md) |
Expand Down
Loading