---
title: WIF mit AWS verwenden
url: https://platform.claude.com/docs/de/manage-claude/wif-providers/aws
description: Authentifiziere AWS-Workloads auf Lambda, EC2, ECS oder EKS gegenüber der Claude API mit Workload Identity Federation und von STS ausgestellten Identitätstoken.
---

AWS-Workloads können sich ohne statische API-Keys gegenüber der Claude API authentifizieren, indem sie ein von AWS signiertes OIDC-Identitätstoken austauschen. Der empfohlene Weg ruft die AWS-STS-API [`GetWebIdentityToken`](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html) auf, die überall funktioniert, wo der Workload über AWS-Anmeldedaten verfügt: Lambda, EC2, ECS und EKS. EKS-Workloads können alternativ den [Kubernetes-Pfad mit projizierten Token](https://platform.claude.com/docs/de/manage-claude/wif-providers/aws#use-eks-projected-service-account-tokens) verwenden, der weniger Konfigurationsschritte erfordert, aber nur innerhalb eines Pods funktioniert.

Dieser Leitfaden zeigt beide Wege. Die zugrunde liegenden Konzepte (Service-Accounts, Federation-Issuer und Federation-Regeln) findest du unter [Workload Identity Federation](https://platform.claude.com/docs/de/manage-claude/workload-identity-federation).

## Voraussetzungen

* Vertrautheit mit den [WIF-Konzepten](https://platform.claude.com/docs/de/manage-claude/workload-identity-federation#concepts): Service-Accounts, Federation-Issuer und Federation-Regeln.
* Ein AWS-Workload (EKS-Pod, ECS-Task, Lambda-Funktion oder EC2-Instanz) mit einer zugewiesenen IAM-Rolle.
* Die `aws`-CLI oder ein AWS SDK, das im Workload verfügbar ist.
* Berechtigung zum Erstellen von Service-Accounts, Federation-Issuern und Federation-Regeln in der Claude Console für deine Anthropic-Organisation.

## STS-Web-Identity-Token verwenden (empfohlen)

Die AWS-STS-API `GetWebIdentityToken` gibt ein von AWS signiertes OIDC-Token zurück, das die IAM-Identität des Aufrufers bestätigt. Da sie die vorhandenen AWS-Anmeldedaten des Workloads nutzt, deckt dieselbe Integration Lambda, EC2, ECS und EKS ab.

### AWS konfigurieren

<Steps>
  <Step title="Ausgehende Web-Identity-Federation für das Konto aktivieren">
    Dies ist ein Flag auf Kontoebene, das standardmäßig deaktiviert ist. Öffne in der AWS-Konsole **IAM**, wähle **Account settings** und aktiviere **Outbound web identity federation**. Um es programmatisch zu aktivieren:

    ```bash
    python3 -c "import boto3; boto3.client('iam').enable_outbound_web_identity_federation()"
    ```

    Wenn dies nicht aktiviert ist, schlagen Aufrufe von `GetWebIdentityToken` mit `OutboundWebIdentityFederationDisabledException` fehl.
  </Step>

  <Step title="Der IAM-Rolle des Workloads die Berechtigung zum Aufruf der API erteilen">
    Hänge diese Richtlinie an die IAM-Rolle an, unter der deine Lambda-Funktion, EC2-Instanz oder dein ECS-Task ausgeführt wird:

    ```json
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["sts:GetWebIdentityToken"],
          "Resource": "*"
        }
      ]
    }
    ```
  </Step>

  <Step title="Die STS-Issuer-URL deines Kontos ermitteln">
    Nach dem Aktivieren der ausgehenden Federation zeigt die Seite **IAM > Account settings** ein Feld **Get Token Issuer URL** mit einem Wert der Form `https://<uuid>.tokens.sts.global.api.aws`. Diese URL ist für dein AWS-Konto eindeutig; kopiere sie für den nächsten Schritt. Um sie programmatisch abzurufen:

    ```bash
    python3 -c "import boto3; print(boto3.client('iam').get_outbound_web_identity_federation_info())"
    ```
  </Step>
</Steps>

### Anthropic konfigurieren

Gehe in der Claude Console zur Seite [Workload identity](https://platform.claude.com/settings/workload-identity-federation) (`platform.claude.com/settings/workload-identity-federation`), klicke auf **Connect workload** und wähle die Kachel **AWS**. Der Assistent führt dich durch die Registrierung des Issuers, das Erstellen eines Service-Accounts und das Erstellen einer Federation-Regel.

Der Assistent erstellt diese Ressourcen für dich. Verwende die folgenden Werte, unabhängig davon, ob du sie im Assistenten eingibst oder an die [Admin API](https://platform.claude.com/docs/de/manage-claude/wif-admin-api) sendest:

**Federation-Issuer:** Registriere die kontospezifische STS-Issuer-URL, die du im vorherigen Schritt kopiert hast. Sie stellt einen öffentlichen JWKS-Endpunkt bereit, verwende daher den Discovery-Modus.

```json
{
  "name": "aws-sts",
  "issuer_url": "https://<uuid>.tokens.sts.global.api.aws",
  "jwks": { "type": "discovery" }
}
```

**Federation-Regel:** Gleiche die Audience ab, die du an `GetWebIdentityToken` übergibst, sowie den IAM-Rollen-ARN der aufrufenden Rolle im `sub`-Claim. Der `sub`-Wert ist der IAM-Rollen-ARN des Workloads, der die API aufgerufen hat, in der Form `arn:aws:iam::<account>:role/<role-name>`. Das Token enthält außerdem einen `https://sts.amazonaws.com/`-Claim mit `aws_account`, `org_id`, `principal_id` und allen `request_tags`, die du übergeben hast; für eine feinere Steuerung kannst du diese mit der `claims`-Map der Regel oder einer CEL-`condition` abgleichen.

```json
{
  "name": "prod-inference",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "arn:aws:iam::123456789012:role/inference-worker",
    "audience": "https://api.anthropic.com"
  },
  "target": { "type": "service_account", "service_account_id": "svac_..." },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

Sei so spezifisch, wie der Workload es zulässt. Gleiche den exakten Rollen-ARN ab und erweitere `subject_prefix` (zum Beispiel auf `arn:aws:iam::123456789012:role/*`) nur dann, wenn mehrere IAM-Rollen demselben Anthropic-Service-Account zugeordnet werden sollen.

### Token abrufen und verwenden

Rufe `GetWebIdentityToken` mit `https://api.anthropic.com` als Audience auf und übergib das Ergebnis dann über `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`) an die Federation-Credentials des SDK. Außer in Java (siehe den Kommentar im Java-Beispiel) nimmt diese Option einen Token-Provider entgegen, sodass das SDK STS bei jeder Aktualisierung erneut aufruft.

<Note>
  `GetWebIdentityToken` ist nur auf regionalen STS-Endpunkten verfügbar. Wenn du `'STS' object has no attribute 'get_web_identity_token'` oder einen ähnlichen Fehler erhältst, binde deinen STS-Client an eine Region (zum Beispiel `boto3.client("sts", region_name="us-east-1")`) und stelle sicher, dass dein AWS SDK aktuell genug ist, um die API zu enthalten.
</Note>

<CodeGroup>
  ```bash cURL
  JWT=$(aws sts get-web-identity-token \
    --region us-east-1 \
    --audience "https://api.anthropic.com" \
    --signing-algorithm RS256 \
    --duration-seconds 900 \
    --query WebIdentityToken --output text)

  RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
    -H "content-type: application/json" \
    --data @- <<JSON
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$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
  )

  ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)

  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 from AWS"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
  ```

  ```python Python
  import os

  import anthropic
  import boto3
  from anthropic import WorkloadIdentityCredentials


  def get_sts_web_identity_token() -> str:
      sts = boto3.client("sts", region_name="us-east-1")
      resp = sts.get_web_identity_token(
          Audience=["https://api.anthropic.com"],
          SigningAlgorithm="RS256",
          DurationSeconds=900,
      )
      return resp["WebIdentityToken"]


  client = anthropic.Anthropic(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=get_sts_web_identity_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 from AWS"}],
  )
  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";
  import { STSClient, GetWebIdentityTokenCommand } from "@aws-sdk/client-sts";

  const sts = new STSClient({ region: "us-east-1" });

  async function getStsWebIdentityToken(): Promise<string> {
    const out = await sts.send(
      new GetWebIdentityTokenCommand({
        Audience: ["https://api.anthropic.com"],
        SigningAlgorithm: "RS256",
        DurationSeconds: 900
      })
    );
    return out.WebIdentityToken!;
  }

  const client = new Anthropic({
    credentials: oidcFederationProvider({
      identityTokenProvider: getStsWebIdentityToken,
      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 from AWS" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```go Go
  ctx := context.TODO()
  cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
  if err != nil {
  	panic(err)
  }
  stsClient := sts.NewFromConfig(cfg)

  getStsToken := option.IdentityTokenFunc(func(ctx context.Context) (string, error) {
  	out, err := stsClient.GetWebIdentityToken(ctx, &sts.GetWebIdentityTokenInput{
  		Audience:         []string{"https://api.anthropic.com"},
  		SigningAlgorithm: "RS256",
  		DurationSeconds:  aws.Int32(900),
  	})
  	if err != nil {
  		return "", err
  	}
  	return *out.WebIdentityToken, nil
  })

  client := anthropic.NewClient(
  	option.WithFederationTokenProvider(getStsToken, 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(ctx, anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Hello from AWS")),
  	},
  })
  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 software.amazon.awssdk.regions.Region;
  import software.amazon.awssdk.services.sts.StsClient;
  import software.amazon.awssdk.services.sts.model.GetWebIdentityTokenRequest;

  void main() {
      StsClient sts = StsClient.builder().region(Region.US_EAST_1).build();

      // Das Java SDK nimmt das Token selbst oder einen Dateipfad, keine Funktion; mit
      // source("value") enthält path(...) das Token. Das SDK sendet dasselbe Token
      // bei jeder Aktualisierung des Anthropic-Zugriffstokens, was fehlschlägt, sobald es
      // abgelaufen ist oder, falls es einen jti-Claim hat, bereits verwendet wurde. Für länger laufende
      // Prozesse übergib source("file") und schreibe vor jeder Aktualisierung ein neues Token
      // in die Datei.
      String stsToken = sts.getWebIdentityToken(
                      GetWebIdentityTokenRequest.builder()
                              .audience("https://api.anthropic.com")
                              .signingAlgorithm("RS256")
                              .durationSeconds(900)
                              .build())
              .webIdentityToken();

      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(stsToken)
                                      .build())
                              .build())
                      .build()))
              .build();

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

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

  ```csharp C#
  using Amazon.SecurityToken;
  using Amazon.SecurityToken.Model;
  // ...
  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 StsTokenProvider(),
  });
  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 from AWS" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }

  class StsTokenProvider : IIdentityTokenProvider
  {
      private readonly AmazonSecurityTokenServiceClient _sts = new(Amazon.RegionEndpoint.USEast1);

      public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
      {
          var resp = await _sts.GetWebIdentityTokenAsync(new GetWebIdentityTokenRequest
          {
              Audience = ["https://api.anthropic.com"],
              SigningAlgorithm = "RS256",
              DurationSeconds = 900,
          }, ct);
          return resp.WebIdentityToken;
      }
  }
  ```

  ```bash CLI
  TOKEN_FILE=$(mktemp)
  aws sts get-web-identity-token \
    --region us-east-1 \
    --audience "https://api.anthropic.com" \
    --signing-algorithm RS256 \
    --duration-seconds 900 \
    --query WebIdentityToken --output text > "$TOKEN_FILE"

  export ANTHROPIC_IDENTITY_TOKEN_FILE="$TOKEN_FILE"
  # ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID sowie
  # ANTHROPIC_SERVICE_ACCOUNT_ID und ANTHROPIC_WORKSPACE_ID werden aus der Umgebung gelesen
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello from AWS"}'
  ```

  ```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;
  use Aws\Sts\StsClient;

  $sts = new StsClient(['region' => 'us-east-1', 'version' => 'latest']);

  class StsIdentityToken implements IdentityTokenProvider
  {
      public function __construct(private StsClient $sts) {}

      public function getIdentityToken(): string
      {
          return $this->sts->getWebIdentityToken([
              'Audience' => ['https://api.anthropic.com'],
              'SigningAlgorithm' => 'RS256',
              'DurationSeconds' => 900,
          ])['WebIdentityToken'];
      }
  }

  $client = new Client(credentials: new CredentialResult(
      provider: new TokenCache(new WorkloadIdentityCredentials(
          identityProvider: new StsIdentityToken($sts),
          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 from AWS']],
  );
  $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
  echo $textBlock->text, PHP_EOL;
  ```

  ```ruby Ruby
  require "anthropic"
  require "aws-sdk-sts"

  sts = Aws::STS::Client.new(region: "us-east-1")
  client = Anthropic::Client.new(
    credentials: Anthropic::WorkloadIdentityCredentials.new(
      identity_token_provider: -> {
        sts.get_web_identity_token(
          audience: ["https://api.anthropic.com"],
          signing_algorithm: "RS256",
          duration_seconds: 900,
        ).web_identity_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 from AWS"}]
  )
  puts message.content.find { it.type == :text }.text
  ```
</CodeGroup>

### Einrichtung überprüfen

Tausche innerhalb des Workloads ein von STS ausgestelltes Token direkt aus und prüfe die Antwort:

```bash cURL
JWT=$(aws sts get-web-identity-token \
  --region us-east-1 \
  --audience "https://api.anthropic.com" \
  --signing-algorithm RS256 \
  --duration-seconds 900 \
  --query WebIdentityToken --output text)

curl -sS https://api.anthropic.com/v1/oauth/token \
  -H "content-type: application/json" \
  -d "{
    \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\",
    \"assertion\": \"$JWT\",
    \"federation_rule_id\": \"fdrl_...\",
    \"organization_id\": \"00000000-0000-0000-0000-000000000000\",
    \"service_account_id\": \"svac_...\",
    \"workspace_id\": \"wrkspc_...\"
  }" | jq
```

Ein erfolgreicher Austausch gibt ein `access_token`, das mit `sk-ant-oat01-` beginnt, und einen `expires_in`-Wert in Sekunden zurück. Wenn der Austausch mit der undurchsichtigen `401`-`authentication_error`-Antwort (Meldung `Authentication failed`) fehlschlägt, prüfe die [Seite mit dem Authentifizierungsverlauf](https://platform.claude.com/settings/workload-identity-federation?tab=history) in der Claude Console auf den Ablehnungsgrund und sieh dir [Fehlerbehebung bei einem fehlgeschlagenen Austausch](https://platform.claude.com/docs/de/manage-claude/wif-reference#troubleshoot-a-failed-exchange) an; die häufigste Ursache auf AWS-Seite ist eine `iss`-Abweichung (die kontospezifische STS-Issuer-URL muss exakt mit der registrierten `issuer_url` übereinstimmen).

## Projizierte EKS-Service-Account-Token verwenden

Wenn dein Workload in einem EKS-Pod läuft, kannst du den STS-Aufruf überspringen und ein von Kubernetes projiziertes Service-Account-Token direkt von der Festplatte lesen. Kubernetes projiziert nativ ein OIDC-kompatibles Token in den Pod, und das SDK kann es von einem Dateipfad lesen, sodass kein Token-Provider-Callable erforderlich ist. Dieser Weg hat zwei AWS-Konfigurationsschritte weniger als der STS-Weg, funktioniert aber nur innerhalb eines Pods; der zugrunde liegende Mechanismus ist derselbe wie bei der [generischen Kubernetes-Integration](https://platform.claude.com/docs/de/manage-claude/wif-providers/kubernetes).

Dieser Weg erfordert zusätzlich einen EKS-Cluster mit [aktiviertem IAM-OIDC-Provider](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) sowie `kubectl`-Zugriff auf den Cluster.

### EKS-Cluster konfigurieren

<Steps>
  <Step title="Die OIDC-Issuer-URL deines Clusters ermitteln">
    Jeder EKS-Cluster hat einen eindeutigen OIDC-Issuer. Rufe ihn mit der AWS CLI ab:

    ```bash CLI
    aws eks describe-cluster \
      --name <cluster-name> \
      --query "cluster.identity.oidc.issuer" \
      --output text
    ```

    Die Ausgabe sieht aus wie `https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...`. Du registrierst diese URL im nächsten Abschnitt als Federation-Issuer.
  </Step>

  <Step title="Service-Account erstellen und ein Token mit Anthropic-Audience projizieren">
    Der EKS-Pod-Identity-Webhook erkennt die Annotation `eks.amazonaws.com/role-arn` und projiziert automatisch ein Token mit `aud: sts.amazonaws.com`, dessen Pfad als `AWS_WEB_IDENTITY_TOKEN_FILE` bereitgestellt wird. Dieses Token dient der AWS-Rollenübernahme. Projiziere für den Anthropic-Austausch ein zweites Token mit `audience: https://api.anthropic.com` und mounte es unter einem eigenen Pfad.

    ```yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: inference-worker
      namespace: inference
      annotations:
        eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/inference-worker
    ```

    ```yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: inference-worker
      namespace: inference
    spec:
      serviceAccountName: inference-worker
      volumes:
        - name: anthropic-token
          projected:
            sources:
              - serviceAccountToken:
                  audience: https://api.anthropic.com
                  expirationSeconds: 3600
                  path: token
      containers:
        - name: app
          image: your-registry/inference-worker:latest
          env:
            - name: ANTHROPIC_IDENTITY_TOKEN_FILE
              value: /var/run/secrets/anthropic.com/token
            - name: ANTHROPIC_FEDERATION_RULE_ID
              value: fdrl_...
            - name: ANTHROPIC_ORGANIZATION_ID
              value: 00000000-0000-0000-0000-000000000000
            - name: ANTHROPIC_SERVICE_ACCOUNT_ID
              value: svac_...
            - name: ANTHROPIC_WORKSPACE_ID  # required when the rule covers multiple workspaces
              value: wrkspc_...
          volumeMounts:
            - name: anthropic-token
              mountPath: /var/run/secrets/anthropic.com
              readOnly: true
    ```
  </Step>

  <Step title="Die Claim-Struktur des Tokens beachten">
    Das projizierte Token ist ein JSON Web Token (JWT), das vom OIDC-Issuer deines Clusters signiert ist. Sein `sub`-Claim folgt der Kubernetes-Konvention `system:serviceaccount:<namespace>:<service-account-name>`:

    ```json
    {
      "iss": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
      "sub": "system:serviceaccount:inference:inference-worker",
      "aud": ["https://api.anthropic.com"],
      "kubernetes.io": {
        "namespace": "inference",
        "serviceaccount": { "name": "inference-worker", "uid": "..." }
      },
      "exp": 1775527120,
      "iat": 1775523520
    }
    ```

    Die `serviceAccountToken`-Projektion setzt `aud` auf `https://api.anthropic.com`. Das separate, von IRSA injizierte Token unter `AWS_WEB_IDENTITY_TOKEN_FILE` trägt `aud: sts.amazonaws.com` und ist für AWS-API-Aufrufe gedacht, nicht für diesen Austausch.
  </Step>
</Steps>

### Anthropic konfigurieren

Gehe in der Claude Console zur Seite [Workload identity](https://platform.claude.com/settings/workload-identity-federation) (`platform.claude.com/settings/workload-identity-federation`), klicke auf **Connect workload** und wähle die Kachel **AWS**. Der Assistent führt dich durch die Registrierung des Issuers, das Erstellen eines Service-Accounts und das Erstellen einer Federation-Regel.

Der Assistent erstellt diese Ressourcen für dich. Verwende die folgenden Werte, unabhängig davon, ob du sie im Assistenten eingibst oder an die [Admin API](https://platform.claude.com/docs/de/manage-claude/wif-admin-api) sendest:

**Federation-Issuer:** EKS-Issuer stellen einen öffentlichen JWKS-Endpunkt bereit, verwende daher den Discovery-Modus. Die Issuer-URL muss exakt mit dem `iss`-Claim des Tokens übereinstimmen. Registriere einen Issuer pro Cluster.

```json
{
  "name": "prod-eks-uswest2",
  "issuer_url": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
  "jwks": { "type": "discovery" }
}
```

**Federation-Regel:** Gleiche den Kubernetes-`sub`-Claim und die Anthropic-Audience `https://api.anthropic.com` ab. (Projiziere ein eigenes Service-Account-Token mit dieser Audience; verwende nicht das IRSA-Standardtoken `sts.amazonaws.com` wieder.)

```json
{
  "name": "prod-inference",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "system:serviceaccount:inference:inference-worker",
    "audience": "https://api.anthropic.com"
  },
  "target": { "type": "service_account", "service_account_id": "svac_..." },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

Sei so spezifisch, wie der Workload es zulässt. Lockere `subject_prefix` nur dann auf `system:serviceaccount:inference:*` (das abschließende `*` macht daraus einen Präfix-Abgleich), wenn jeder Service-Account im Namespace demselben Anthropic-Service-Account zugeordnet werden soll.

### Token abrufen und verwenden

Innerhalb des Pods liegt das projizierte Token unter `/var/run/secrets/anthropic.com/token` (in der Pod-Spezifikation als `ANTHROPIC_IDENTITY_TOKEN_FILE` bereitgestellt). Übergib diese Datei an die Federation-Credentials des SDK, und das SDK übernimmt den Austausch und die Aktualisierung.

<CodeGroup>
  ```bash cURL
  JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")

  RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
    -H "content-type: application/json" \
    --data @- <<JSON
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$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
  )

  ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)

  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 from EKS"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
  ```

  ```python Python
  import os

  import anthropic
  from anthropic import IdentityTokenFile, WorkloadIdentityCredentials

  client = anthropic.Anthropic(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=IdentityTokenFile(
              os.environ["ANTHROPIC_IDENTITY_TOKEN_FILE"]
          ),
          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 from EKS"}],
  )
  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";
  import { identityTokenFromFile } from "@anthropic-ai/sdk/lib/credentials/identity-token";

  const client = new Anthropic({
    credentials: oidcFederationProvider({
      identityTokenProvider: identityTokenFromFile(process.env.ANTHROPIC_IDENTITY_TOKEN_FILE!),
      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 from EKS" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```go Go
  tokenPath := os.Getenv("ANTHROPIC_IDENTITY_TOKEN_FILE")

  readToken := option.IdentityTokenFunc(func(ctx context.Context) (string, error) {
  	raw, err := os.ReadFile(tokenPath)
  	if err != nil {
  		return "", fmt.Errorf("read identity token: %w", err)
  	}
  	return string(raw), nil
  })

  client := anthropic.NewClient(
  	option.WithFederationTokenProvider(readToken, 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 from EKS")),
  	},
  })
  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
  AnthropicClient client = AnthropicOkHttpClient.fromEnv();

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

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

  ```csharp C#
  // Liest ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID und ANTHROPIC_IDENTITY_TOKEN_FILE
  // aus der Umgebung des Pods.
  using var client = new AnthropicClient();

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

  ```bash CLI
  # Liest ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID und ANTHROPIC_IDENTITY_TOKEN_FILE
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello from EKS"}'
  ```

  ```php PHP
  use Anthropic\Client;

  // Liest ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID und ANTHROPIC_IDENTITY_TOKEN_FILE
  $client = new Client();

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

  ```ruby Ruby
  require "anthropic"

  # Liest ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID und ANTHROPIC_IDENTITY_TOKEN_FILE
  client = Anthropic::Client.new

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

<Tip>
  Die Pod-Spezifikation setzt bereits `ANTHROPIC_IDENTITY_TOKEN_FILE`, `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID` und `ANTHROPIC_WORKSPACE_ID`, sodass du den Client ohne Argumente erstellen kannst und das SDK die Federation-Umgebungsvariablen automatisch liest.
</Tip>

### Einrichtung überprüfen

Tausche innerhalb des Pods das projizierte Token direkt aus und prüfe die Antwort:

```bash cURL
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")

curl -sS https://api.anthropic.com/v1/oauth/token \
  -H "content-type: application/json" \
  -d "{
    \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\",
    \"assertion\": \"$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\"
  }" | jq
```

Ein erfolgreicher Austausch gibt ein `access_token`, das mit `sk-ant-oat01-` beginnt, und einen `expires_in`-Wert in Sekunden zurück. Wenn der Austausch mit der undurchsichtigen `401`-`authentication_error`-Antwort (Meldung `Authentication failed`) fehlschlägt, prüfe die [Seite mit dem Authentifizierungsverlauf](https://platform.claude.com/settings/workload-identity-federation?tab=history) in der Claude Console auf den Ablehnungsgrund und sieh dir [Fehlerbehebung bei einem fehlgeschlagenen Austausch](https://platform.claude.com/docs/de/manage-claude/wif-reference#troubleshoot-a-failed-exchange) an; die häufigste Ursache auf EKS-Seite ist, dass das `aud` des projizierten Tokens nicht mit der Regel übereinstimmt (projiziere ein Token mit `audience: https://api.anthropic.com`, nicht mit dem IRSA-Standard `sts.amazonaws.com`).

## Regel eingrenzen

<Warning>
  Ein `subject_prefix` von `arn:aws:iam::123456789012:role/*` passt auf jede IAM-Rolle im Konto. Jeder Principal, der eine beliebige passende Rolle übernehmen kann, kann ein föderiertes Anthropic-Token erhalten.
</Warning>

Beschränke den `match`-Block der Regel auf den engsten Geltungsbereich, der zu deinem Anwendungsfall passt:

* **Den vollständigen Rollen-ARN festlegen:** Verwende `subject_prefix: "arn:aws:iam::<account>:role/<role-name>"` ohne abschließendes `*`, damit andere Rollen im Konto nicht passen.
* **Die Konto-ID festlegen:** Gleiche das Feld `aws_account` des `https://sts.amazonaws.com/`-Claims des Tokens mit der `claims`-Map oder einer CEL-`condition` ab, als zusätzliche Absicherung (Defense in Depth) gegen ein falsch konfiguriertes Präfix.
* **Namespace und Service-Account auf EKS festlegen:** Verwende den exakten Wert `system:serviceaccount:<namespace>:<name>` ohne `*` nach dem Präfix `system:serviceaccount:`.
* **Eine separate Regel pro Umgebung verwenden:** Erstelle getrennte Regeln für Produktions-, Staging- und Entwicklungs-Workloads, anstatt ein Präfix so zu erweitern, dass es alle abdeckt.

## Nächste Schritte

* Sieh dir die [WIF-Referenz](https://platform.claude.com/docs/de/manage-claude/wif-reference) an, um die vollständige Referenz zur Rangfolge der Anmeldedaten, zur Profilkonfiguration und zum Regelabgleich zu erhalten.
* Für selbstverwaltete Kubernetes-Cluster, die nicht auf EKS laufen, siehe [WIF mit Kubernetes verwenden](https://platform.claude.com/docs/de/manage-claude/wif-providers/kubernetes).
