# BYOK를 사용하는 Azure 관리 ID

GitHub Copilot SDK의 BYOK(사용자 고유의 키 가져오기)은 정적 API 키를 지원하지만 Azure 배포에서는 수명이 긴 키 대신 Microsoft Entra ID(관리 ID)를 사용하는 경우가 많습니다. GitHub Copilot SDK는 유연성을 극대화하기 위해 Azure ID SDK로 구성하도록 설계되었습니다. Azure ID SDK API를 사용하여 요청 시 새 토큰을 가져올 수 있는 전달자 토큰 공급자 콜백을 제공합니다.

<!-- markdownlint-disable GHD046 GHD005 -->

<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

이 가이드에서는 Azure ID SDK API를 사용하여 GitHub Copilot SDK를 통해 Microsoft Foundry 모델로 인증하는 방법을 보여 줍니다. 대부분의 언어는 `DefaultAzureCredential`를 사용합니다. Rust는 로컬에서는 `DeveloperToolsCredential`를, Azure에서는 `ManagedIdentityCredential`를 사용합니다.

## 작동 방식

Microsoft Foundry의 OpenAI 호환 엔드포인트(`https://<resource-name>.openai.azure.com/openai/v1/`)는 정적 API 키 대신 Microsoft Entra ID 전달자 토큰을 허용합니다. 이 가이드에서는 토큰 공급자 콜백을 사용하므로 GitHub Copilot SDK 런타임에서 요청 시 새 토큰을 요청할 수 있습니다.

Python 예로 사용하는 흐름은 다음과 같습니다.

1. 사용자 환경에 맞게 `DefaultAzureCredential` 구성합니다.
2. `bearer_token_provider`를 사용하여 `DefaultAzureCredential` 범위에 대한 토큰을 가져오는 콜백을 BYOK 프로바이더 구성의 `https://ai.azure.com/.default`에 전달합니다.
3. GitHub Copilot SDK가 해당 콜백을 통해 요청 시 새 토큰을 요청하도록 합니다.

![다이어그램: 설명된 프로세스를 보여 주는 시퀀스 다이어그램](/assets/images/help/copilot/copilot-sdk/setup-azure-managed-identity-diagram-0.png)

## 코드 샘플

### 사전 요구 사항

언어에 대한 Azure ID 및 GitHub Copilot SDK 패키지를 설치합니다.

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

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

```bash
dotnet add package GitHub.Copilot.SDK
dotnet add package Azure.Core
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

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

```bash
go get github.com/github/copilot-sdk/go
go get github.com/Azure/azure-sdk-for-go/sdk/azidentity
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

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

```xml
<dependency>
    <groupId>com.github</groupId>
    <artifactId>copilot-sdk-java</artifactId>
    <version>${copilot.sdk.version}</version>
</dependency>

<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-identity</artifactId>
    <version>${azure.identity.version}</version>
</dependency>
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

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

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

</div>

<div class="ghd-codetab" data-lang="rust" data-label="Rust"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Rust</div>

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

```bash
cargo add github-copilot-sdk azure_identity azure_core
cargo add tokio --features macros,rt-multi-thread
```

</div>

<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

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

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

</div>

</div>

### 토큰 공급자 콜백 사용

GitHub Copilot SDK 런타임에서 제공하는 콜백을 통해 요청 시 새 토큰을 요청하려는 경우 이 방법을 사용합니다. Azure ID SDK는 토큰 캐싱 및 새로 고침 타이밍을 처리합니다.

언어별 구현은 다음과 같습니다.

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

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

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

DefaultAzureCredential credential = new(
    DefaultAzureCredential.DefaultEnvironmentVariableName);
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}/openai/v1/",
        BearerTokenProvider = async _ =>
        {
            AccessToken token = await credential.GetTokenAsync(
                new TokenRequestContext(["https://ai.azure.com/.default"]));
            return token.Token;
        },
        WireApi = "responses",
    },
});

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

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

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

```golang
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
    "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
    copilot "github.com/github/copilot-sdk/go"
)
func main() {
    opts := azidentity.DefaultAzureCredentialOptions{RequireAzureTokenCredentials: true}
    credential, err := azidentity.NewDefaultAzureCredential(&opts)
    if err != nil {
        log.Fatal(err)
    }

    getBearerToken := func(args copilot.ProviderTokenArgs) (string, error) {
        token, err := credential.GetToken(context.Background(), policy.TokenRequestOptions{
            Scopes: []string{"https://ai.azure.com/.default"},
        })
        if err != nil {
            return "", err
        }
        return token.Token, nil
    }

    client := copilot.NewClient(nil)
    if err := client.Start(context.Background()); err != nil {
        log.Fatal(err)
    }
    defer client.Stop()

    foundryURL := os.Getenv("FOUNDRY_RESOURCE_URL")

    session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{
        Model: "gpt-5.5",
        Provider: &copilot.ProviderConfig{
            Type:                "openai",
            BaseURL:             fmt.Sprintf("%s/openai/v1/", foundryURL),
            BearerTokenProvider: getBearerToken,
            WireAPI:             "responses",
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    defer session.Disconnect()

    ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
    defer cancel()

    response, err := session.SendAndWait(ctx, copilot.MessageOptions{
        Prompt: "Hello from Managed Identity!",
    })
    if err != nil {
        log.Fatal(err)
    }

    if response != nil {
        if data, ok := response.Data.(*copilot.AssistantMessageData); ok {
            fmt.Println(data.Content)
        }
    }
}
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

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

```java
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.AzureIdentityEnvVars;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.github.copilot.CopilotClient;
import com.github.copilot.generated.AssistantMessageEvent;
import com.github.copilot.rpc.BearerTokenProvider;
import com.github.copilot.rpc.MessageOptions;
import com.github.copilot.rpc.ProviderConfig;
import com.github.copilot.rpc.SessionConfig;

public class ManagedIdentityExample {
    public static void main(String[] args) throws Exception {
        var credential = new DefaultAzureCredentialBuilder()
                .requireEnvVars(AzureIdentityEnvVars.AZURE_TOKEN_CREDENTIALS)
                .build();
        BearerTokenProvider tokenProvider = providerArgs ->
            credential
                .getToken(new TokenRequestContext().addScopes("https://ai.azure.com/.default"))
                .map(accessToken -> accessToken.getToken())
                .toFuture();
        String foundryUrl = System.getenv("FOUNDRY_RESOURCE_URL");

        try (var client = new CopilotClient()) {
            client.start().get();

            var session = client.createSession(new SessionConfig()
                    .setModel("gpt-5.5")
                    .setProvider(new ProviderConfig()
                            .setType("openai")
                            .setBaseUrl(foundryUrl + "/openai/v1/")
                            .setBearerTokenProvider(tokenProvider)
                            .setWireApi("responses")))
                .get();

            AssistantMessageEvent response = session
                    .sendAndWait(new MessageOptions().setPrompt("Hello from Managed Identity!"))
                    .get();
            System.out.println(response.getData().content());

            session.disconnect().get();
        }
    }
}
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

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

```python
import asyncio
import os

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

async def main():
    credential = DefaultAzureCredential(require_envvar=True)
    async def get_bearer_token(_args) -> str:
        token = await credential.get_token("https://ai.azure.com/.default")
        return token.token

    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-5.5",
        provider=ProviderConfig(
            type="openai",
            base_url=f"{foundry_url.rstrip('/')}/openai/v1/",
            bearer_token_provider=get_bearer_token,
            wire_api="responses",
        ),
    )

    response = await session.send_and_wait("Hello from Managed Identity!")
    print(response.data.content)

    await client.stop()
    await credential.close()

asyncio.run(main())
```

</div>

<div class="ghd-codetab" data-lang="rust" data-label="Rust"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Rust</div>

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

```rust
use std::sync::Arc;

use azure_core::credentials::TokenCredential;
use azure_identity::{DeveloperToolsCredential, ManagedIdentityCredential};
use github_copilot_sdk::{BearerTokenError, Client, ClientOptions, MessageOptions, ProviderTokenArgs};
use github_copilot_sdk::types::{ProviderConfig, SessionConfig};

fn credential_for_environment() -> azure_core::Result<Arc<dyn TokenCredential>> {
    match std::env::var("AZURE_TOKEN_CREDENTIALS").as_deref() {
        Ok("ManagedIdentityCredential") => Ok(ManagedIdentityCredential::new(None)?),
        _ => Ok(DeveloperToolsCredential::new(None)?),
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let credential = credential_for_environment()?;
    let foundry_url = std::env::var("FOUNDRY_RESOURCE_URL")?;

    let get_bearer_token = {
        let credential = credential.clone();
        move |_args: ProviderTokenArgs| {
            let credential = credential.clone();
            async move {
                let token = credential
                    .get_token(&["https://ai.azure.com/.default"], None)
                    .await
                    .map_err(|err| BearerTokenError::message(err.to_string()))?;
                Ok(token.token.secret().to_string())
            }
        }
    };

    let mut provider = ProviderConfig::default();
    provider.provider_type = Some("openai".to_string());
    provider.base_url = format!("{}/openai/v1/", foundry_url.trim_end_matches('/'));
    provider.bearer_token_provider = Some(Arc::new(get_bearer_token));
    provider.wire_api = Some("responses".to_string());

    let mut config = SessionConfig::default();
    config.model = Some("gpt-5.5".to_string());
    config.provider = Some(provider);

    let client = Client::start(ClientOptions::default()).await?;
    let session = client.create_session(config).await?;

    session
        .send_and_wait(MessageOptions::new("Hello from Managed Identity!"))
        .await?;

    session.disconnect().await?;
    client.stop().await?;
    Ok(())
}
```

</div>

<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

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

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

const credential = new DefaultAzureCredential({
  requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"],
});
const getBearerToken = async () => {
  const tokenResponse = await credential.getToken("https://ai.azure.com/.default");
  return tokenResponse.token;
};

const client = new CopilotClient();

const session = await client.createSession({
  model: "gpt-5.5",
  provider: {
    type: "openai",
    baseUrl: `${process.env.FOUNDRY_RESOURCE_URL}/openai/v1/`,
    bearerTokenProvider: getBearerToken,
    wireApi: "responses",
  },
});

const response = await session.sendAndWait({ prompt: "Hello from Managed Identity!" });
console.log(response?.data.content);

await client.stop();
```

</div>

</div>

## 환경 구성

| 변수                                                                                                                               | Description                            | Example                                  |
| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------- |
| `AZURE_TOKEN_CREDENTIALS`                                                                                                        |                                        |                                          |
| **Azure**에서 실행하는 경우 `ManagedIdentityCredential`로 설정합니다.                                                                          |                                        |                                          |
| **로컬에서** 실행할 때는 이를 `dev` 또는 `AzureCliCredential`와 같은 개발자 도구 자격 증명 이름으로 설정하세요.                                                    | `ManagedIdentityCredential`            |                                          |
| `AZURE_CLIENT_ID`                                                                                                                |                                        |                                          |
| *선택 사항*.                                                                                                                         |                                        |                                          |
| **Azure**에서 실행하는 경우, `ManagedIdentityCredential`를 사용할 때 이 값을 사용자 할당 관리형 ID의 클라이언트 ID로 설정합니다. 설정하지 않으면 Azure 시스템 할당 관리 ID를 사용합니다. | `11111111-2222-3333-4444-555555555555` |                                          |
| `FOUNDRY_RESOURCE_URL`                                                                                                           | 사용자의 Microsoft Foundry 리소스 URL         | `https://<my-resource>.openai.azure.com` |

API 키 환경 변수가 필요하지 않습니다. 인증은 Azure ID 자격 증명에 의해 처리됩니다. .NET Go, Java, Python 및 TypeScript는 `DefaultAzureCredential` 자동으로 다음을 지원합니다.

* **관리 ID**(시스템 할당 또는 사용자 할당): Azure 호스팅 앱
* **Azure CLI**(`az login`): 로컬 개발용
* **환경 변수** (`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`): 서비스 주체의 경우
* **워크로드 ID**: Kubernetes의 경우

.NET, Go, Java, Python 및 TypeScript에서는 `ManagedIdentityCredential`가 사용자 할당 관리 ID를 선택하기 위해 `AZURE_CLIENT_ID`를 읽습니다. Rust는 이 가이드의 예외입니다.

Rust에서는 로컬 개발 시 `DeveloperToolsCredential`를 사용하고 Azure에서 실행할 때는 `ManagedIdentityCredential`를 사용합니다. 다른 언어의 경우 전체 자격 증명 체인에 대한 `DefaultAzureCredential` 설명서를 참조하세요.

* [.NET](https://aka.ms/azsdk/net/identity/credential-chains#defaultazurecredential-overview)
* [Go](https://aka.ms/azsdk/go/identity/credential-chains#defaultazurecredential-overview)
* [Java](https://aka.ms/azsdk/java/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)

## 이 패턴을 사용하는 경우

| Scenario                                                                 | Recommendation |
| ------------------------------------------------------------------------ | -------------- |
| 관리 ID를 사용하는 Azure 호스팅 앱                                                  |                |
| ✅ 이 패턴 사용                                                                |                |
| 기존 Microsoft Entra 서비스 주체를 사용하는 앱                                        |                |
| ✅ 이 패턴 사용                                                                |                |
| `az login`를 사용한 로컬 개발                                                    |                |
| ✅ 이 패턴 사용                                                                |                |
| 정적 API 키를 사용하는 비 Azure 환경                                                |                |
| [BYOK(사용자 고유의 키 가져오기)](/ko/copilot/how-tos/copilot-sdk/auth/byok) 사용     |                |
| GitHub Copilot 구독 사용 가능                                                  |                |
| [GitHub OAuth 설정](/ko/copilot/how-tos/copilot-sdk/setup/github-oauth) 사용 |                |

## 참고하십시오

* [BYOK(사용자 고유의 키 가져오기)](/ko/copilot/how-tos/copilot-sdk/auth/byok): 정적 API 키 구성
* [백 엔드 서비스 설정](/ko/copilot/how-tos/copilot-sdk/setup/backend-services): 서버 쪽 배포