diff --git a/docs/setup/azure-managed-identity.md b/docs/setup/azure-managed-identity.md index c803c7f89..bf90ed99c 100644 --- a/docs/setup/azure-managed-identity.md +++ b/docs/setup/azure-managed-identity.md @@ -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 Foundry-->>SDK: Model response SDK-->>App: Session events ``` -## Python example +## Code samples + +
+.NET + +### Prerequisites + +Install the required packages: + +```bash +dotnet add package GitHub.Copilot.SDK +dotnet add package Azure.Core +``` + +### Basic usage + + + +```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" })); + +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); +``` + +
+ +
+Python ### Prerequisites +Install the required packages: + ```bash pip install github-copilot-sdk azure-identity ``` ### Basic usage + + ```python import asyncio import os @@ -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 - 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/", @@ -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: + + ```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) 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/", @@ -124,25 +181,41 @@ class ManagedIdentityCopilotAgent: return response.data.content if response else "" ``` -## Node.js / TypeScript example +
+ +
+TypeScript + +### Prerequisites + +Install the required packages: + +```bash +npm install @github/copilot-sdk @azure/identity +``` + +### Basic usage + ```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" ); 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", }, @@ -154,43 +227,14 @@ console.log(response?.data.content); await client.stop(); ``` -## .NET example - - -```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); -``` +
## 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`. | | +| `FOUNDRY_RESOURCE_URL` | Your Microsoft Foundry resource URL | `https://.openai.azure.com` | No API key environment variable is needed—authentication is handled by `DefaultAzureCredential`, which automatically supports: @@ -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) |