{"meta":{"title":"BYOK를 사용하는 Azure 관리 ID","intro":"GitHub Copilot SDK의 BYOK(사용자 고유의 키 가져오기)은 정적 API 키를 지원하지만 Azure 배포에서는 수명이 긴 키 대신 Microsoft Entra ID(관리 ID)를 사용하는 경우가 많습니다. GitHub Copilot SDK는 유연성을 극대화하기 위해 Azure ID SDK로 구성하도록 설계되었습니다. Azure ID SDK API를 사용하여 요청 시 새 토큰을 가져올 수 있는 전달자 토큰 공급자 콜백을 제공합니다.","product":"GitHub Copilot","breadcrumbs":[{"href":"/ko/copilot","title":"GitHub Copilot"},{"href":"/ko/copilot/how-tos","title":"방법"},{"href":"/ko/copilot/how-tos/copilot-sdk","title":"코필로트 SDK"},{"href":"/ko/copilot/how-tos/copilot-sdk/setup","title":"Copilot SDK 설정"},{"href":"/ko/copilot/how-tos/copilot-sdk/setup/azure-managed-identity","title":"Azure 관리 ID"}],"documentType":"article"},"body":"# BYOK를 사용하는 Azure 관리 ID\n\nGitHub Copilot SDK의 BYOK(사용자 고유의 키 가져오기)은 정적 API 키를 지원하지만 Azure 배포에서는 수명이 긴 키 대신 Microsoft Entra ID(관리 ID)를 사용하는 경우가 많습니다. GitHub Copilot SDK는 유연성을 극대화하기 위해 Azure ID SDK로 구성하도록 설계되었습니다. Azure ID SDK API를 사용하여 요청 시 새 토큰을 가져올 수 있는 전달자 토큰 공급자 콜백을 제공합니다.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n이 가이드에서는 Azure ID SDK API를 사용하여 GitHub Copilot SDK를 통해 Microsoft Foundry 모델로 인증하는 방법을 보여 줍니다. 대부분의 언어는 `DefaultAzureCredential`를 사용합니다. Rust는 로컬에서는 `DeveloperToolsCredential`를, Azure에서는 `ManagedIdentityCredential`를 사용합니다.\n\n## 작동 방식\n\nMicrosoft Foundry의 OpenAI 호환 엔드포인트(`https://<resource-name>.openai.azure.com/openai/v1/`)는 정적 API 키 대신 Microsoft Entra ID 전달자 토큰을 허용합니다. 이 가이드에서는 토큰 공급자 콜백을 사용하므로 GitHub Copilot SDK 런타임에서 요청 시 새 토큰을 요청할 수 있습니다.\n\nPython 예로 사용하는 흐름은 다음과 같습니다.\n\n1. 사용자 환경에 맞게 `DefaultAzureCredential` 구성합니다.\n2. `bearer_token_provider`를 사용하여 `DefaultAzureCredential` 범위에 대한 토큰을 가져오는 콜백을 BYOK 프로바이더 구성의 `https://ai.azure.com/.default`에 전달합니다.\n3. GitHub Copilot SDK가 해당 콜백을 통해 요청 시 새 토큰을 요청하도록 합니다.\n\n![다이어그램: 설명된 프로세스를 보여 주는 시퀀스 다이어그램](/assets/images/help/copilot/copilot-sdk/setup-azure-managed-identity-diagram-0.png)\n\n## 코드 샘플\n\n### 사전 요구 사항\n\n언어에 대한 Azure ID 및 GitHub Copilot SDK 패키지를 설치합니다.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n<!-- docs-validate: skip -->\n\n```bash\ndotnet add package GitHub.Copilot.SDK\ndotnet add package Azure.Core\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```bash\ngo get github.com/github/copilot-sdk/go\ngo get github.com/Azure/azure-sdk-for-go/sdk/azidentity\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```xml\n<dependency>\n    <groupId>com.github</groupId>\n    <artifactId>copilot-sdk-java</artifactId>\n    <version>${copilot.sdk.version}</version>\n</dependency>\n\n<dependency>\n    <groupId>com.azure</groupId>\n    <artifactId>azure-identity</artifactId>\n    <version>${azure.identity.version}</version>\n</dependency>\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```bash\npip install github-copilot-sdk azure-identity\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n<!-- docs-validate: skip -->\n\n```bash\ncargo add github-copilot-sdk azure_identity azure_core\ncargo add tokio --features macros,rt-multi-thread\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n<!-- docs-validate: skip -->\n\n```bash\nnpm install @github/copilot-sdk @azure/identity\n```\n\n</div>\n\n</div>\n\n### 토큰 공급자 콜백 사용\n\nGitHub Copilot SDK 런타임에서 제공하는 콜백을 통해 요청 시 새 토큰을 요청하려는 경우 이 방법을 사용합니다. Azure ID SDK는 토큰 캐싱 및 새로 고침 타이밍을 처리합니다.\n\n언어별 구현은 다음과 같습니다.\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n<!-- docs-validate: skip -->\n\n```csharp\nusing Azure.Core;\nusing Azure.Identity;\nusing GitHub.Copilot;\n\nDefaultAzureCredential credential = new(\n    DefaultAzureCredential.DefaultEnvironmentVariableName);\nawait using CopilotClient client = new();\nstring foundryUrl = Environment.GetEnvironmentVariable(\"FOUNDRY_RESOURCE_URL\")!;\n\nawait using CopilotSession session = await client.CreateSessionAsync(new SessionConfig\n{\n    Model = \"gpt-5.5\",\n    Provider = new ProviderConfig\n    {\n        Type = \"openai\",\n        BaseUrl = $\"{foundryUrl}/openai/v1/\",\n        BearerTokenProvider = async _ =>\n        {\n            AccessToken token = await credential.GetTokenAsync(\n                new TokenRequestContext([\"https://ai.azure.com/.default\"]));\n            return token.Token;\n        },\n        WireApi = \"responses\",\n    },\n});\n\nAssistantMessageEvent? response = await session.SendAndWaitAsync(\n    new MessageOptions { Prompt = \"Hello from Managed Identity!\" });\nConsole.WriteLine(response?.Data.Content);\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```golang\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n    \"os\"\n    \"time\"\n\n    \"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy\"\n    \"github.com/Azure/azure-sdk-for-go/sdk/azidentity\"\n    copilot \"github.com/github/copilot-sdk/go\"\n)\nfunc main() {\n    opts := azidentity.DefaultAzureCredentialOptions{RequireAzureTokenCredentials: true}\n    credential, err := azidentity.NewDefaultAzureCredential(&opts)\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    getBearerToken := func(args copilot.ProviderTokenArgs) (string, error) {\n        token, err := credential.GetToken(context.Background(), policy.TokenRequestOptions{\n            Scopes: []string{\"https://ai.azure.com/.default\"},\n        })\n        if err != nil {\n            return \"\", err\n        }\n        return token.Token, nil\n    }\n\n    client := copilot.NewClient(nil)\n    if err := client.Start(context.Background()); err != nil {\n        log.Fatal(err)\n    }\n    defer client.Stop()\n\n    foundryURL := os.Getenv(\"FOUNDRY_RESOURCE_URL\")\n\n    session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{\n        Model: \"gpt-5.5\",\n        Provider: &copilot.ProviderConfig{\n            Type:                \"openai\",\n            BaseURL:             fmt.Sprintf(\"%s/openai/v1/\", foundryURL),\n            BearerTokenProvider: getBearerToken,\n            WireAPI:             \"responses\",\n        },\n    })\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer session.Disconnect()\n\n    ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n    defer cancel()\n\n    response, err := session.SendAndWait(ctx, copilot.MessageOptions{\n        Prompt: \"Hello from Managed Identity!\",\n    })\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    if response != nil {\n        if data, ok := response.Data.(*copilot.AssistantMessageData); ok {\n            fmt.Println(data.Content)\n        }\n    }\n}\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.azure.core.credential.TokenRequestContext;\nimport com.azure.identity.AzureIdentityEnvVars;\nimport com.azure.identity.DefaultAzureCredentialBuilder;\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.generated.AssistantMessageEvent;\nimport com.github.copilot.rpc.BearerTokenProvider;\nimport com.github.copilot.rpc.MessageOptions;\nimport com.github.copilot.rpc.ProviderConfig;\nimport com.github.copilot.rpc.SessionConfig;\n\npublic class ManagedIdentityExample {\n    public static void main(String[] args) throws Exception {\n        var credential = new DefaultAzureCredentialBuilder()\n                .requireEnvVars(AzureIdentityEnvVars.AZURE_TOKEN_CREDENTIALS)\n                .build();\n        BearerTokenProvider tokenProvider = providerArgs ->\n            credential\n                .getToken(new TokenRequestContext().addScopes(\"https://ai.azure.com/.default\"))\n                .map(accessToken -> accessToken.getToken())\n                .toFuture();\n        String foundryUrl = System.getenv(\"FOUNDRY_RESOURCE_URL\");\n\n        try (var client = new CopilotClient()) {\n            client.start().get();\n\n            var session = client.createSession(new SessionConfig()\n                    .setModel(\"gpt-5.5\")\n                    .setProvider(new ProviderConfig()\n                            .setType(\"openai\")\n                            .setBaseUrl(foundryUrl + \"/openai/v1/\")\n                            .setBearerTokenProvider(tokenProvider)\n                            .setWireApi(\"responses\")))\n                .get();\n\n            AssistantMessageEvent response = session\n                    .sendAndWait(new MessageOptions().setPrompt(\"Hello from Managed Identity!\"))\n                    .get();\n            System.out.println(response.getData().content());\n\n            session.disconnect().get();\n        }\n    }\n}\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```python\nimport asyncio\nimport os\n\nfrom azure.identity.aio import DefaultAzureCredential\nfrom copilot import CopilotClient\nfrom copilot.session import PermissionHandler, ProviderConfig\n\nasync def main():\n    credential = DefaultAzureCredential(require_envvar=True)\n    async def get_bearer_token(_args) -> str:\n        token = await credential.get_token(\"https://ai.azure.com/.default\")\n        return token.token\n\n    foundry_url = os.environ[\"FOUNDRY_RESOURCE_URL\"]\n\n    client = CopilotClient()\n    await client.start()\n\n    session = await client.create_session(\n        on_permission_request=PermissionHandler.approve_all,\n        model=\"gpt-5.5\",\n        provider=ProviderConfig(\n            type=\"openai\",\n            base_url=f\"{foundry_url.rstrip('/')}/openai/v1/\",\n            bearer_token_provider=get_bearer_token,\n            wire_api=\"responses\",\n        ),\n    )\n\n    response = await session.send_and_wait(\"Hello from Managed Identity!\")\n    print(response.data.content)\n\n    await client.stop()\n    await credential.close()\n\nasyncio.run(main())\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"rust\" data-label=\"Rust\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Rust</div>\n\n<!-- docs-validate: skip -->\n\n```rust\nuse std::sync::Arc;\n\nuse azure_core::credentials::TokenCredential;\nuse azure_identity::{DeveloperToolsCredential, ManagedIdentityCredential};\nuse github_copilot_sdk::{BearerTokenError, Client, ClientOptions, MessageOptions, ProviderTokenArgs};\nuse github_copilot_sdk::types::{ProviderConfig, SessionConfig};\n\nfn credential_for_environment() -> azure_core::Result<Arc<dyn TokenCredential>> {\n    match std::env::var(\"AZURE_TOKEN_CREDENTIALS\").as_deref() {\n        Ok(\"ManagedIdentityCredential\") => Ok(ManagedIdentityCredential::new(None)?),\n        _ => Ok(DeveloperToolsCredential::new(None)?),\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    let credential = credential_for_environment()?;\n    let foundry_url = std::env::var(\"FOUNDRY_RESOURCE_URL\")?;\n\n    let get_bearer_token = {\n        let credential = credential.clone();\n        move |_args: ProviderTokenArgs| {\n            let credential = credential.clone();\n            async move {\n                let token = credential\n                    .get_token(&[\"https://ai.azure.com/.default\"], None)\n                    .await\n                    .map_err(|err| BearerTokenError::message(err.to_string()))?;\n                Ok(token.token.secret().to_string())\n            }\n        }\n    };\n\n    let mut provider = ProviderConfig::default();\n    provider.provider_type = Some(\"openai\".to_string());\n    provider.base_url = format!(\"{}/openai/v1/\", foundry_url.trim_end_matches('/'));\n    provider.bearer_token_provider = Some(Arc::new(get_bearer_token));\n    provider.wire_api = Some(\"responses\".to_string());\n\n    let mut config = SessionConfig::default();\n    config.model = Some(\"gpt-5.5\".to_string());\n    config.provider = Some(provider);\n\n    let client = Client::start(ClientOptions::default()).await?;\n    let session = client.create_session(config).await?;\n\n    session\n        .send_and_wait(MessageOptions::new(\"Hello from Managed Identity!\"))\n        .await?;\n\n    session.disconnect().await?;\n    client.stop().await?;\n    Ok(())\n}\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n<!-- docs-validate: skip -->\n\n```typescript\nimport { DefaultAzureCredential } from \"@azure/identity\";\nimport { CopilotClient } from \"@github/copilot-sdk\";\n\nconst credential = new DefaultAzureCredential({\n  requiredEnvVars: [\"AZURE_TOKEN_CREDENTIALS\"],\n});\nconst getBearerToken = async () => {\n  const tokenResponse = await credential.getToken(\"https://ai.azure.com/.default\");\n  return tokenResponse.token;\n};\n\nconst client = new CopilotClient();\n\nconst session = await client.createSession({\n  model: \"gpt-5.5\",\n  provider: {\n    type: \"openai\",\n    baseUrl: `${process.env.FOUNDRY_RESOURCE_URL}/openai/v1/`,\n    bearerTokenProvider: getBearerToken,\n    wireApi: \"responses\",\n  },\n});\n\nconst response = await session.sendAndWait({ prompt: \"Hello from Managed Identity!\" });\nconsole.log(response?.data.content);\n\nawait client.stop();\n```\n\n</div>\n\n</div>\n\n## 환경 구성\n\n| 변수                                                                                                                               | Description                            | Example                                  |\n| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------- |\n| `AZURE_TOKEN_CREDENTIALS`                                                                                                        |                                        |                                          |\n| **Azure**에서 실행하는 경우 `ManagedIdentityCredential`로 설정합니다.                                                                          |                                        |                                          |\n| **로컬에서** 실행할 때는 이를 `dev` 또는 `AzureCliCredential`와 같은 개발자 도구 자격 증명 이름으로 설정하세요.                                                    | `ManagedIdentityCredential`            |                                          |\n| `AZURE_CLIENT_ID`                                                                                                                |                                        |                                          |\n| *선택 사항*.                                                                                                                         |                                        |                                          |\n| **Azure**에서 실행하는 경우, `ManagedIdentityCredential`를 사용할 때 이 값을 사용자 할당 관리형 ID의 클라이언트 ID로 설정합니다. 설정하지 않으면 Azure 시스템 할당 관리 ID를 사용합니다. | `11111111-2222-3333-4444-555555555555` |                                          |\n| `FOUNDRY_RESOURCE_URL`                                                                                                           | 사용자의 Microsoft Foundry 리소스 URL         | `https://<my-resource>.openai.azure.com` |\n\nAPI 키 환경 변수가 필요하지 않습니다. 인증은 Azure ID 자격 증명에 의해 처리됩니다. .NET Go, Java, Python 및 TypeScript는 `DefaultAzureCredential` 자동으로 다음을 지원합니다.\n\n* **관리 ID**(시스템 할당 또는 사용자 할당): Azure 호스팅 앱\n* **Azure CLI**(`az login`): 로컬 개발용\n* **환경 변수** (`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`): 서비스 주체의 경우\n* **워크로드 ID**: Kubernetes의 경우\n\n.NET, Go, Java, Python 및 TypeScript에서는 `ManagedIdentityCredential`가 사용자 할당 관리 ID를 선택하기 위해 `AZURE_CLIENT_ID`를 읽습니다. Rust는 이 가이드의 예외입니다.\n\nRust에서는 로컬 개발 시 `DeveloperToolsCredential`를 사용하고 Azure에서 실행할 때는 `ManagedIdentityCredential`를 사용합니다. 다른 언어의 경우 전체 자격 증명 체인에 대한 `DefaultAzureCredential` 설명서를 참조하세요.\n\n* [.NET](https://aka.ms/azsdk/net/identity/credential-chains#defaultazurecredential-overview)\n* [Go](https://aka.ms/azsdk/go/identity/credential-chains#defaultazurecredential-overview)\n* [Java](https://aka.ms/azsdk/java/identity/credential-chains#defaultazurecredential-overview)\n* [Python](https://aka.ms/azsdk/python/identity/credential-chains#defaultazurecredential-overview)\n* [TypeScript](https://aka.ms/azsdk/js/identity/credential-chains#defaultazurecredential-overview)\n\n## 이 패턴을 사용하는 경우\n\n| Scenario                                                                 | Recommendation |\n| ------------------------------------------------------------------------ | -------------- |\n| 관리 ID를 사용하는 Azure 호스팅 앱                                                  |                |\n| ✅ 이 패턴 사용                                                                |                |\n| 기존 Microsoft Entra 서비스 주체를 사용하는 앱                                        |                |\n| ✅ 이 패턴 사용                                                                |                |\n| `az login`를 사용한 로컬 개발                                                    |                |\n| ✅ 이 패턴 사용                                                                |                |\n| 정적 API 키를 사용하는 비 Azure 환경                                                |                |\n| [BYOK(사용자 고유의 키 가져오기)](/ko/copilot/how-tos/copilot-sdk/auth/byok) 사용     |                |\n| GitHub Copilot 구독 사용 가능                                                  |                |\n| [GitHub OAuth 설정](/ko/copilot/how-tos/copilot-sdk/setup/github-oauth) 사용 |                |\n\n## 참고하십시오\n\n* [BYOK(사용자 고유의 키 가져오기)](/ko/copilot/how-tos/copilot-sdk/auth/byok): 정적 API 키 구성\n* [백 엔드 서비스 설정](/ko/copilot/how-tos/copilot-sdk/setup/backend-services): 서버 쪽 배포"}