---
title: Okta와 함께 WIF 사용하기
url: https://platform.claude.com/docs/ko/manage-claude/wif-providers/okta
description: Workload Identity Federation을 사용하여 Okta 서비스 애플리케이션 ID를 Claude API에 페더레이션합니다.
---

Okta는 OAuth 2.0 `client_credentials` 그랜트를 통해 **서비스 애플리케이션**에 OIDC 액세스 토큰을 발급함으로써 워크로드 ID 공급자 역할을 할 수 있습니다. 워크로드는 Okta에 인증하고(일반적으로 `private_key_jwt`를 사용하므로 공유 시크릿이 저장되지 않습니다), 서명된 "JSON Web Token"(JSON 웹 토큰), 즉 JWT를 받은 다음, 해당 JWT를 Anthropic과 교환하여 수명이 짧은 액세스 토큰을 얻습니다.

Okta 인가 서버의 발급자(issuer) URL은 `https://<your-domain>.okta.com/oauth2/<auth-server-id>` 형식을 따릅니다. 내장된 기본 서버를 사용하는 경우 경로는 `/oauth2/default`입니다.

<Note>
  Okta **커스텀 인가 서버**(`default` 서버 포함)를 사용해야 합니다. Okta 조직(org) 인가 서버(경로에 인가 서버 ID가 없는 `/oauth2/v1/token` 엔드포인트)에서 직접 발급된 토큰은 Okta가 해당 토큰의 서명 키를 게시하지 않기 때문에 외부 당사자가 검증할 수 없습니다.
</Note>

Okta를 구성하고 인증하는 방법은 이 문서의 범위를 벗어나는 다양한 방식이 있습니다. 구성 및 인증 메커니즘이 회사의 지침과 보안 관행을 따르는지 확인하세요.

## 사전 요구 사항

* [WIF 개념](https://platform.claude.com/docs/ko/manage-claude/workload-identity-federation#concepts)에 대한 이해: 서비스 계정, 페더레이션 발급자, 페더레이션 규칙.
* API Access Management가 활성화된 Okta 조직(커스텀 인가 서버에 필요).
* Anthropic 조직의 Claude Console에서 서비스 계정, 페더레이션 발급자, 페더레이션 규칙을 생성할 수 있는 권한.
* Okta의 `/v1/token` 엔드포인트에서 토큰을 요청하고 `api.anthropic.com`에 도달할 수 있는 워크로드.

## Okta 구성

개략적으로 다음 작업이 필요합니다:

1. Okta 서비스 애플리케이션을 생성합니다.
2. 기본 인가 서버를 구성하거나(또는 새 커스텀 인가 서버를 생성하여) 오디언스(audience), 스코프(scope), 액세스 정책, 그리고 매칭에 사용할 커스텀 클레임을 설정합니다.

정확한 탐색 경로는 Okta 조직 구성과 관리 콘솔 버전에 따라 다릅니다. 다음 번호가 매겨진 단계는 일반적인 경로 중 하나를 안내합니다:

1. **서비스 앱 통합을 생성합니다.** Okta Admin Console에서 **API Services** 유형(OIDC, machine-to-machine)의 새 앱 통합을 생성합니다. 생성된 **Client ID**를 기록해 두세요.
2. **클라이언트 인증을 구성합니다.** 키리스(keyless) 설정의 경우 **Public key / Private key**(`private_key_jwt`)를 선택하고 워크로드의 공개 JWK를 등록합니다. 또는 환경에서 안전하게 저장할 수 있다면 클라이언트 시크릿을 사용할 수도 있습니다. 다음 예제의 경우 애플리케이션에서 DPoP 요구 사항을 비활성화해야 할 수 있습니다. 프로덕션 설정이 조직의 보안 요구 사항을 준수하는지 확인하세요.
3. **오디언스를 설정합니다.** 커스텀 인가 서버에서 오디언스를 `https://api.anthropic.com`으로 설정하여 발급된 액세스 토큰이 해당 `aud` 클레임을 포함하도록 합니다. Anthropic은 이 고정 값에 대해 `aud`를 검증합니다.
4. **스코프를 부여합니다.** 커스텀 인가 서버에서 서비스 앱이 요청할 수 있는 스코프가 하나 이상 존재하는지 확인합니다(예: `anthropic.access`). Okta는 부여된 스코프를 포함하지 않는 `client_credentials` 요청을 거부합니다.
5. **액세스 정책을 생성합니다.** 커스텀 인가 서버에서 서비스 앱이 4단계에서 부여한 스코프를 요청할 수 있도록 허용하는 규칙이 하나 이상 포함된 액세스 정책을 생성합니다.
6. **(선택 사항) 커스텀 클레임을 추가합니다.** 클라이언트 ID 이외의 항목으로 매칭하려면 인가 서버의 **Claims** 탭에서 액세스 토큰에 클레임을 추가합니다.

`client_credentials`를 사용하는 서비스 앱의 경우, Okta는 발급된 액세스 토큰의 `sub` 클레임을 애플리케이션의 **Client ID**로, `iss`를 인가 서버의 발급자 URL로 설정합니다.

## Anthropic 구성

Claude Console에서 [Workload identity](https://platform.claude.com/settings/workload-identity-federation) 페이지(`platform.claude.com/settings/workload-identity-federation`)로 이동하여 **Connect workload**를 클릭하고 **Custom OIDC**를 선택합니다. 마법사가 발급자 등록, 서비스 계정 생성, 페더레이션 규칙 생성 과정을 안내합니다.

마법사가 이러한 리소스를 대신 생성해 줍니다. 마법사에 입력하든 [Admin API](https://platform.claude.com/docs/ko/manage-claude/wif-admin-api)로 전송하든 다음 값을 사용하세요:

**페더레이션 발급자:** Okta 커스텀 인가 서버 URL과 디스커버리 모드를 사용합니다. Anthropic은 Okta의 `.well-known/openid-configuration` 디스커버리 문서를 읽고, 해당 문서가 알리는 `jwks_uri`에서 JWKS를 가져옵니다.

```json
{
  "name": "okta-prod",
  "issuer_url": "https://acme.okta.com/oauth2/aus1a2b3c4d5e6f7g8h9",
  "jwks": { "type": "discovery" }
}
```

**페더레이션 규칙:** 서비스 앱의 Client ID인 Okta `sub` 클레임으로 매칭합니다. Okta에서 커스텀 클레임을 정의한 경우 `claims` 맵 또는 CEL `condition`을 사용하여 해당 클레임으로 대신 매칭할 수 있습니다.

```json
{
  "name": "okta-pipeline",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "0oa1b2c3d4e5f6g7h8i9",
    "audience": "https://api.anthropic.com"
  },
  "target": { "type": "service_account", "service_account_id": "svac_..." },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

## 토큰 획득 및 Claude API 호출

워크로드의 런타임 내부에서 토큰을 사용할 수 있게 해 주는(프로젝션된 파일 또는 로컬 메타데이터 엔드포인트를 통해) 플랫폼 네이티브 공급자(AWS, Google Cloud, Kubernetes)와 달리 Okta는 그렇지 않습니다. 워크로드는 Okta의 토큰 엔드포인트를 호출하여 JWT를 얻은 다음, 해당 JWT를 ID 토큰으로 Anthropic SDK에 전달해야 합니다.

<CodeGroup>
  ```bash cURL
  # 1. Okta에서 액세스 토큰을 요청합니다(private_key_jwt를 사용한 client_credentials).
  OKTA_JWT=$(curl -sS "https://acme.okta.com/oauth2/aus1a2b3c4d5e6f7g8h9/v1/token" \
    -d grant_type=client_credentials \
    -d scope=anthropic.access \
    -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
    --data-urlencode client_assertion="$SIGNED_CLIENT_ASSERTION" \
    | jq -r .access_token)

  # 2. Okta JWT를 Anthropic 액세스 토큰으로 교환합니다.
  ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
    -H "content-type: application/json" \
    -d @- <<JSON | jq -r .access_token
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$OKTA_JWT",
    "federation_rule_id": "$ANTHROPIC_FEDERATION_RULE_ID",
    "organization_id": "$ANTHROPIC_ORGANIZATION_ID",
    "service_account_id": "$ANTHROPIC_SERVICE_ACCOUNT_ID",
    "workspace_id": "$ANTHROPIC_WORKSPACE_ID"
  }
  JSON
  )

  # 3. Claude API를 호출합니다.
  curl https://api.anthropic.com/v1/messages \
    -H "authorization: Bearer $ACCESS_TOKEN" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{"model": "claude-opus-5-5", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello, Claude"}]}' \
    | jq -r '.content[] | select(.type == "text") | .text'
  ```

  ```python Python
  import os
  import httpx2
  import anthropic
  from anthropic import WorkloadIdentityCredentials


  def fetch_okta_token() -> str:
      response = httpx2.post(
          f"{os.environ['OKTA_ISSUER']}/v1/token",
          data={
              "grant_type": "client_credentials",
              "scope": "anthropic.access",
              "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
              # Okta 앱의 개인 키로 서명한 RFC 7523 client_assertion JWT를 생성합니다
              "client_assertion": build_signed_client_assertion(),
          },
      )
      response.raise_for_status()
      return response.json()["access_token"]


  client = anthropic.Anthropic(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=fetch_okta_token,
          federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
          organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
          service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
          workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
      ),
  )

  message = client.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      messages=[{"role": "user", "content": "Hello, Claude"}],
  )
  print(next(block.text for block in message.content if block.type == "text"))
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";
  import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";

  async function fetchOktaToken(): Promise<string> {
    const response = await fetch(`${process.env.OKTA_ISSUER}/v1/token`, {
      method: "POST",
      headers: { "content-type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({
        grant_type: "client_credentials",
        scope: "anthropic.access",
        client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
        // Okta 앱의 개인 키로 서명한 RFC 7523 client_assertion JWT를 생성합니다
        client_assertion: buildSignedClientAssertion()
      })
    });
    const body = (await response.json()) as { access_token: string };
    return body.access_token;
  }

  const client = new Anthropic({
    credentials: oidcFederationProvider({
      identityTokenProvider: fetchOktaToken,
      federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
      organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
      serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
      workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
      baseURL: "https://api.anthropic.com",
      fetch
    })
  });

  const message = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello, Claude" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```go Go
  package main

  import (
  	"context"
  	"encoding/json"
  	"fmt"
  	"net/http"
  	"net/url"
  	"os"
  	"strings"

  	"github.com/anthropics/anthropic-sdk-go"
  	"github.com/anthropics/anthropic-sdk-go/option"
  )

  func fetchOktaToken(ctx context.Context) (string, error) {
  	form := url.Values{
  		"grant_type":            {"client_credentials"},
  		"scope":                 {"anthropic.access"},
  		"client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},
  		// Okta 앱의 개인 키로 서명한 RFC 7523 client_assertion JWT를 생성합니다
  		"client_assertion": {buildSignedClientAssertion()},
  	}
  	req, err := http.NewRequestWithContext(ctx, http.MethodPost,
  		os.Getenv("OKTA_ISSUER")+"/v1/token", strings.NewReader(form.Encode()))
  	if err != nil {
  		return "", err
  	}
  	req.Header.Set("content-type", "application/x-www-form-urlencoded")
  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		return "", err
  	}
  	defer resp.Body.Close()
  	var body struct {
  		AccessToken string `json:"access_token"`
  	}
  	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
  		return "", err
  	}
  	return body.AccessToken, nil
  }

  func main() {
  	client := anthropic.NewClient(
  		option.WithFederationTokenProvider(option.IdentityTokenFunc(fetchOktaToken), option.FederationOptions{
  			FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
  			OrganizationID:   os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
  			ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
  			WorkspaceID:      os.Getenv("ANTHROPIC_WORKSPACE_ID"),
  		}),
  	)
  	message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  		Model:     anthropic.ModelClaudeOpus5_5,
  		MaxTokens: 1024,
  		Messages: []anthropic.MessageParam{
  			anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
  		},
  	})
  	if err != nil {
  		panic(err)
  	}
  	for _, block := range message.Content {
  		if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  			fmt.Println(textBlock.Text)
  			break
  		}
  	}
  }
  ```

  ```java Java
  import com.anthropic.config.AuthenticationConfig;
  import com.anthropic.config.AuthenticationType;
  import com.anthropic.config.IdentityTokenConfig;
  import com.anthropic.config.InMemoryProfileConfigProvider;
  import com.anthropic.config.ProfileConfig;
  // ...
  import com.fasterxml.jackson.databind.ObjectMapper;
  import java.net.URI;
  import java.net.URLEncoder;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.util.Map;
  import java.util.stream.Collectors;
  import static java.nio.charset.StandardCharsets.UTF_8;

  void main() throws Exception {
      var form = Map.of(
                      "grant_type", "client_credentials",
                      "scope", "anthropic.access",
                      "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
                      // Okta 앱의 개인 키로 서명한 RFC 7523 client_assertion JWT를 생성합니다
                      "client_assertion", buildSignedClientAssertion())
              .entrySet().stream()
              .map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), UTF_8))
              .collect(Collectors.joining("&"));
      var request = HttpRequest.newBuilder(URI.create(System.getenv("OKTA_ISSUER") + "/v1/token"))
              .header("content-type", "application/x-www-form-urlencoded")
              .POST(HttpRequest.BodyPublishers.ofString(form))
              .build();

      // Java SDK는 함수가 아니라 토큰 자체 또는 파일 경로를 받습니다.
      // source("value")를 사용하면 path(...)에 토큰이 담깁니다. SDK는 Anthropic 액세스 토큰을
      // 갱신할 때마다 동일한 토큰을 전송하므로, 토큰이 만료되었거나
      // jti 클레임이 있는 경우 이미 사용되었다면 실패합니다. 장시간 실행되는
      // 프로세스에서는 source("file")을 전달하고 매 갱신 전에
      // 파일에 새 토큰을 기록하세요.
      var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
      String oktaToken = new ObjectMapper().readTree(response.body()).get("access_token").asText();

      AnthropicClient client = AnthropicOkHttpClient.builder()
              .configurationProvider(InMemoryProfileConfigProvider.of(ProfileConfig.builder()
                      .organizationId(System.getenv("ANTHROPIC_ORGANIZATION_ID"))
                      .workspaceId(System.getenv("ANTHROPIC_WORKSPACE_ID"))
                      .authentication(AuthenticationConfig.builder()
                              .type(AuthenticationType.OIDC_FEDERATION)
                              .federationRuleId(System.getenv("ANTHROPIC_FEDERATION_RULE_ID"))
                              .serviceAccountId(System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
                              .identityToken(IdentityTokenConfig.builder()
                                      .source("value")
                                      .path(oktaToken)
                                      .build())
                              .build())
                      .build()))
              .build();

      var message = client.messages().create(MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(1024)
              .addUserMessage("Hello, Claude")
              .build());

      IO.println(message.content());
  }
  ```

  ```csharp C#
  using Anthropic.Credentials;
  // ...

  var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
  {
      FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
      OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
      ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
      WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
      IdentityTokenProvider = new OktaTokenProvider(),
  });
  using var client = new AnthropicClient(new ClientOptions { Credentials = credentials });

  var message = await client.Messages.Create(new()
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Messages = [new() { Role = Role.User, Content = "Hello, Claude" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }

  class OktaTokenProvider : IIdentityTokenProvider
  {
      private static readonly HttpClient Http = new();

      public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
      {
          var form = new FormUrlEncodedContent(new Dictionary<string, string>
          {
              ["grant_type"] = "client_credentials",
              ["scope"] = "anthropic.access",
              ["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
              // Okta 앱의 개인 키로 서명한 RFC 7523 client_assertion JWT를 생성합니다
              ["client_assertion"] = BuildSignedClientAssertion(),
          });
          var response = await Http.PostAsync(
              $"{Environment.GetEnvironmentVariable("OKTA_ISSUER")}/v1/token", form, ct);
          response.EnsureSuccessStatusCode();
          using var json = await JsonDocument.ParseAsync(
              await response.Content.ReadAsStreamAsync(ct), default, ct);
          return json.RootElement.GetProperty("access_token").GetString()!;
      }
  }
  ```

  ```bash CLI
  # 1. Okta에서 액세스 토큰을 요청하고 임시 파일에 기록합니다.
  ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
  curl -sS "$OKTA_ISSUER/v1/token" \
    -d grant_type=client_credentials \
    -d scope=anthropic.access \
    -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
    --data-urlencode client_assertion="$SIGNED_CLIENT_ASSERTION" \
    | jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
  export ANTHROPIC_IDENTITY_TOKEN_FILE

  # 2. Claude API를 호출합니다. CLI는 ANTHROPIC_FEDERATION_RULE_ID,
  # ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID 및
  # ANTHROPIC_IDENTITY_TOKEN_FILE을 읽고 토큰 교환을 수행합니다.
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello, Claude"}'
  ```

  ```php PHP
  use Anthropic\Client;
  use Anthropic\Lib\Credentials\CredentialResult;
  use Anthropic\Lib\Credentials\IdentityTokenProvider;
  use Anthropic\Lib\Credentials\TokenCache;
  use Anthropic\Lib\Credentials\WorkloadIdentityCredentials;

  class OktaIdentityToken implements IdentityTokenProvider
  {
      public function getIdentityToken(): string
      {
          $ch = curl_init(getenv('OKTA_ISSUER') . '/v1/token');
          curl_setopt_array($ch, [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_POSTFIELDS => http_build_query([
                  'grant_type' => 'client_credentials',
                  'scope' => 'anthropic.access',
                  'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
                  // Okta 앱의 개인 키로 서명한 RFC 7523 client_assertion JWT를 생성합니다
                  'client_assertion' => buildSignedClientAssertion(),
              ]),
          ]);
          $body = json_decode(curl_exec($ch), true);
          curl_close($ch);
          return $body['access_token'];
      }
  }

  $client = new Client(
      credentials: new CredentialResult(
          provider: new TokenCache(new WorkloadIdentityCredentials(
              identityProvider: new OktaIdentityToken(),
              federationRuleId: getenv('ANTHROPIC_FEDERATION_RULE_ID'),
              organizationId: getenv('ANTHROPIC_ORGANIZATION_ID'),
              serviceAccountId: getenv('ANTHROPIC_SERVICE_ACCOUNT_ID'),
              workspaceId: getenv('ANTHROPIC_WORKSPACE_ID') ?: null,
          )),
      ),
  );

  $message = $client->messages->create(
      model: 'claude-opus-5-5',
      maxTokens: 1024,
      messages: [['role' => 'user', 'content' => 'Hello, Claude']],
  );
  echo array_find($message->content, static fn ($block): bool => $block->type === 'text')->text, PHP_EOL;
  ```

  ```ruby Ruby
  require "anthropic"
  require "json"
  require "net/http"

  def fetch_okta_token
    uri = URI("#{ENV.fetch('OKTA_ISSUER')}/v1/token")
    response = Net::HTTP.post_form(
      uri,
      "grant_type" => "client_credentials",
      "scope" => "anthropic.access",
      "client_assertion_type" => "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
      # Okta 앱의 개인 키로 서명한 RFC 7523 client_assertion JWT를 생성합니다
      "client_assertion" => build_signed_client_assertion
    )
    JSON.parse(response.body).fetch("access_token")
  end

  client = Anthropic::Client.new(
    credentials: Anthropic::WorkloadIdentityCredentials.new(
      identity_token_provider: -> { fetch_okta_token },
      federation_rule_id: ENV.fetch("ANTHROPIC_FEDERATION_RULE_ID"),
      organization_id: ENV.fetch("ANTHROPIC_ORGANIZATION_ID"),
      service_account_id: ENV.fetch("ANTHROPIC_SERVICE_ACCOUNT_ID"),
      workspace_id: ENV["ANTHROPIC_WORKSPACE_ID"]
    )
  )

  message = client.messages.create(
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{role: "user", content: "Hello, Claude"}]
  )
  puts message.content.find { it.type == :text }.text
  ```
</CodeGroup>

Java를 제외하고(해당 예제의 주석 참조), SDK 예제는 Okta 토큰 가져오기 함수를 토큰 제공자로서 `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`)에 전달합니다. Claude SDK는 Anthropic 액세스 토큰이 만료에 가까워질 때마다 이를 호출하므로, Okta 토큰 가져오기 함수는 토큰을 무기한 캐싱하지 말고 호출될 때마다 새 토큰을 반환해야 합니다. `ant` CLI는 교환할 때마다 `ANTHROPIC_IDENTITY_TOKEN_FILE`을 다시 읽으므로, 장시간 실행되는 셸에서는 타이머를 사용하여 해당 파일을 갱신하세요.

## 설정 검증

교환에 성공하면 `sk-ant-oat01-`로 시작하는 `access_token`과 초 단위의 `expires_in` 값이 반환됩니다. 교환이 불투명한 `401` `authentication_error` 응답(메시지 `Authentication failed`)으로 실패하면, Claude Console의 [인증 기록 페이지](https://platform.claude.com/settings/workload-identity-federation?tab=history)에서 거부 사유를 확인하고 [실패한 교환 문제 해결](https://platform.claude.com/docs/ko/manage-claude/wif-reference#troubleshoot-a-failed-exchange)을 참조하세요. Okta 측에서 가장 흔한 원인은 `issuer_url` 불일치입니다(`/oauth2/<auth-server-id>` 경로를 포함해야 하며, Okta 조직 인가 서버는 사용할 수 없습니다).

## 규칙 범위 지정

<Warning>
  동일한 Okta 인가 서버 아래의 여러 서비스 앱은 동일한 발급자를 공유합니다. `subject_prefix`를 생략한 규칙은 해당 서버의 모든 서비스 앱과 매칭되므로, 서비스 앱을 등록할 수 있는 모든 팀이 페더레이션된 Anthropic 토큰을 얻을 수 있습니다.
</Warning>

규칙의 `match` 블록을 사용 사례에 맞는 가장 좁은 범위로 고정하세요:

* **정확한 Client ID 고정:** `subject_prefix`를 후행 `*` 없이 서비스 앱의 전체 Client ID로 설정합니다.
* **오디언스 고정:** 인가 서버에 구성한 `audience` 값과 매칭하여 다른 오디언스용으로 발급된 토큰이 거부되도록 합니다.
* **커스텀 클레임으로 매칭:** 더 세밀한 범위 지정을 위해 인가 서버의 **Claims** 탭에서 클레임을 추가하고 규칙의 `claims` 맵 또는 CEL `condition`으로 매칭합니다.
* **서비스 앱당 하나의 규칙 사용:** 여러 앱에서 하나의 규칙을 공유하는 대신 각 서비스 앱마다 별도의 페더레이션 규칙을 생성합니다.

## 다음 단계

* 전체 자격 증명 확인 순서와 프로필 구성에 대해서는 [WIF 레퍼런스](https://platform.claude.com/docs/ko/manage-claude/wif-reference)를 검토하세요.
* CEL 표현식으로 커스텀 Okta 클레임을 매칭하려면 [WIF 레퍼런스](https://platform.claude.com/docs/ko/manage-claude/wif-reference#rule-matching-semantics)를 참조하세요.
