---
title: WIF mit GitHub Actions verwenden
url: https://platform.claude.com/docs/de/manage-claude/wif-providers/github-actions
description: Authentifiziere GitHub-Actions-Workflows gegenüber der Claude API mit kurzlebigen Identitätstoken anstelle langlebiger API-Keys.
---

Jeder Lauf eines GitHub-Actions-Workflows kann ein signiertes Identitätstoken von GitHubs gehostetem Issuer unter `https://token.actions.githubusercontent.com` anfordern. Mit „Workload Identity Federation“ (Workload-Identitätsföderation), oder WIF, tauscht dein Workflow dieses Token gegen ein kurzlebiges Anthropic-Zugriffstoken ein, sodass deine CI-Jobs die Claude API aufrufen können, ohne dass ein `ANTHROPIC_API_KEY`-Secret in deinem Repository gespeichert ist.

Der `sub`-Claim des Tokens kodiert das Repository und den Auslösekontext. Bei einem Push auf einen Branch hat er die Form `repo:<owner>/<repo>:ref:refs/heads/<branch>`. Pull-Request-Läufe verwenden `repo:<owner>/<repo>:pull_request`, und durch Environments abgesicherte Deployments verwenden `repo:<owner>/<repo>:environment:<name>`. Deine Föderationsregel gleicht diesen Claim (und andere, wie `repository_owner` und `ref`) ab, um zu entscheiden, welche Workflow-Läufe sich authentifizieren dürfen.

## Voraussetzungen

* Vertrautheit mit den [WIF-Konzepten](https://platform.claude.com/docs/de/manage-claude/workload-identity-federation#concepts): Service-Accounts, Föderations-Issuer und Föderationsregeln.
* Ein GitHub-Repository, in dem du Workflow-Dateien bearbeiten und die Berechtigung `id-token: write` erteilen kannst.
* Die Berechtigung, in der Claude Console Service-Accounts, Föderations-Issuer und Föderationsregeln für deine Anthropic-Organisation zu erstellen.
* Deine Anthropic-Organisations-ID. Du findest sie auf der Seite [Organization](https://platform.claude.com/settings/organization) (`platform.claude.com/settings/organization`) in der Claude Console.

## Deinen Workflow konfigurieren

GitHub stellt ein Identitätstoken nur für Jobs aus, die es explizit anfordern. Füge die Berechtigung `id-token: write` auf Workflow- oder Job-Ebene hinzu:

```yaml
permissions:
  id-token: write
  contents: read
```

Innerhalb des Jobs stellt der Runner zwei Umgebungsvariablen bereit: `ACTIONS_ID_TOKEN_REQUEST_URL` und `ACTIONS_ID_TOKEN_REQUEST_TOKEN`. Rufe die Request-URL mit dem Request-Token als Bearer-Credential und deiner gewählten Audience als Query-Parameter auf und schreibe dann das zurückgegebene „JSON Web Token“, oder JWT, in eine Datei:

```yaml
- name: Fetch GitHub OIDC token
  run: |
    curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
      "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://api.anthropic.com" \
      | jq -r .value > /tmp/gha-jwt
```

Wenn du JavaScript bevorzugst, stellt `actions/github-script` dieselbe Funktionalität über `core.getIDToken(audience)` bereit:

```yaml
- name: Fetch GitHub OIDC token
  uses: actions/github-script@v8
  with:
    script: |
      const fs = require('fs');
      const token = await core.getIDToken('https://api.anthropic.com');
      fs.writeFileSync('/tmp/gha-jwt', token);
```

Das dekodierte Token enthält Claims, die den Workflow-Lauf beschreiben. Deine Föderationsregel gleicht gegen diese ab:

```json
{
  "iss": "https://token.actions.githubusercontent.com",
  "sub": "repo:your-org/your-repo:ref:refs/heads/main",
  "aud": "https://api.anthropic.com",
  "repository": "your-org/your-repo",
  "repository_owner": "your-org",
  "ref": "refs/heads/main",
  "sha": "abc123...",
  "workflow": "CI",
  "actor": "octocat",
  "event_name": "push"
}
```

Siehe [GitHubs Referenz zum OIDC-Subject-Claim](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims) für die vollständige Liste der `sub`-Formate.

## 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 **GitHub Actions** aus. Der Assistent führt dich durch die Registrierung des Issuers, das Erstellen eines Service-Accounts und das Erstellen einer Föderationsregel.

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:

**Föderations-Issuer:** GitHub veröffentlicht sein OIDC-Discovery-Dokument und JWKS öffentlich, verwende daher den Discovery-Modus. Anthropic aktualisiert die Schlüssel automatisch, wenn GitHub sie rotiert.

```json
{
  "name": "github-actions",
  "issuer_url": "https://token.actions.githubusercontent.com",
  "jwks": { "type": "discovery" }
}
```

**Föderationsregel:** Gleiche nur die Workflow-Läufe ab, denen du vertrauen möchtest. Siehe [Einschränken, welche Workflows sich authentifizieren können](https://platform.claude.com/docs/de/manage-claude/wif-providers/github-actions#restrict-which-workflows-can-authenticate), um zu erfahren, wie du diese Claims sicher eingrenzt.

```json
{
  "name": "gha-main",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "repo:your-org/your-repo:ref:refs/heads/main",
    "audience": "https://api.anthropic.com",
    "claims": {
      "repository_owner": "your-org"
    }
  },
  "target": {
    "type": "service_account",
    "service_account_id": "svac_..."
  },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

Sei so spezifisch, wie es der Workload erlaubt. Lockere `subject_prefix` nur dann auf `repo:your-org/your-repo:*` (kombiniert mit einer `claims.ref`-Einschränkung), wenn die Regel mehrere Ereignistypen aus demselben Repository abgleichen muss, da das abschließende Segment von `sub` zwischen `ref:...`-, `environment:...`- und `pull_request`-Ereignissen variiert.

## Ein Token beziehen und verwenden

Setze die Föderations-Umgebungsvariablen für den Job und rufe das SDK wie gewohnt auf. `Anthropic()` (typescript: `new Anthropic()`; csharp: `new AnthropicClient()`; go: `anthropic.NewClient()`; java: `AnthropicOkHttpClient.fromEnv()`; php: `new Client()`; ruby: `Anthropic::Client.new`) liest `ANTHROPIC_IDENTITY_TOKEN_FILE`, tauscht das JWT bei der ersten Anfrage ein und aktualisiert das Zugriffstoken automatisch, bevor es abläuft.

<CodeGroup>
  ```yaml Workflow
  name: Call Claude
  on: push

  permissions:
    id-token: write
    contents: read

  jobs:
    call-claude:
      runs-on: ubuntu-latest
      env:
        ANTHROPIC_FEDERATION_RULE_ID: fdrl_...
        ANTHROPIC_ORGANIZATION_ID: 00000000-0000-0000-0000-000000000000
        ANTHROPIC_SERVICE_ACCOUNT_ID: svac_...
        ANTHROPIC_WORKSPACE_ID: wrkspc_...  # required when the rule covers multiple workspaces
        ANTHROPIC_IDENTITY_TOKEN_FILE: /tmp/gha-jwt
      steps:
        - uses: actions/checkout@v5
        - name: Fetch GitHub OIDC token
          run: |
            curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
              "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://api.anthropic.com" \
              | jq -r .value > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
        - name: Run your script
          run: |
            pip install anthropic
            python your_script.py
  ```

  ```bash cURL
  JWT=$(cat /tmp/gha-jwt)

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

  ```python Python
  import anthropic

  # Liest ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID und ANTHROPIC_IDENTITY_TOKEN_FILE
  # aus der Job-Umgebung.
  client = anthropic.Anthropic()

  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";

  // Liest ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID und ANTHROPIC_IDENTITY_TOKEN_FILE
  // aus der Job-Umgebung.
  const client = new Anthropic();

  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
  // Liest ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID und ANTHROPIC_IDENTITY_TOKEN_FILE
  // aus der Job-Umgebung.
  client := anthropic.NewClient()

  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
  AnthropicClient client = AnthropicOkHttpClient.fromEnv();

  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#
  // Liest ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID und ANTHROPIC_IDENTITY_TOKEN_FILE
  // aus der Job-Umgebung.
  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, Claude" }],
  });
  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
  # aus der Job-Umgebung.
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello, Claude"}'
  ```

  ```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
  // aus der Job-Umgebung.
  $client = new Client();

  $message = $client->messages->create(
      model: 'claude-opus-5-5',
      maxTokens: 1024,
      messages: [['role' => 'user', 'content' => 'Hello, Claude']],
  );
  $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
  # aus der Job-Umgebung.
  client = Anthropic::Client.new

  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>

Jedes von GitHub ausgestellte Identitätstoken läuft etwa fünf Minuten nach der Ausstellung ab. Der Token-Request-Endpunkt (`ACTIONS_ID_TOKEN_REQUEST_URL`) bleibt für den gesamten Job gültig, sodass du jederzeit ein frisches Token abrufen kannst. Das SDK tauscht das Token bei der ersten Verwendung ein und cacht das resultierende Anthropic-Zugriffstoken. Bei Jobs, die länger laufen als die Lebensdauer des Anthropic-Tokens, liest das SDK `ANTHROPIC_IDENTITY_TOKEN_FILE` bei jeder Erneuerung erneut ein; führe daher den Abrufschritt regelmäßig erneut aus (oder verpacke ihn in eine Hintergrundschleife), um die Datei aktuell zu halten. Alternativ kannst du dem SDK, außer in Java, einen Token-Provider übergeben, der `ACTIONS_ID_TOKEN_REQUEST_URL` direkt aufruft, anstatt den Dateipfad zu verwenden.

## Die Einrichtung überprüfen

Ein erfolgreicher Austausch gibt ein `access_token`, das mit `sk-ant-oat01-` beginnt, und einen `expires_in`-Wert in Sekunden zurück. Ein abgelehnter Austausch gibt einen undurchsichtigen `401`-`authentication_error` mit der festen Meldung `Authentication failed` zurück, unabhängig davon, welche Prüfung fehlgeschlagen ist; in den meisten Fällen wird der Ablehnungsgrund im Eintrag des Versuchs auf der [Seite mit dem Authentifizierungsverlauf](https://platform.claude.com/settings/workload-identity-federation?tab=history) in der Claude Console festgehalten, und [Einen fehlgeschlagenen Austausch beheben](https://platform.claude.com/docs/de/manage-claude/wif-reference#troubleshoot-a-failed-exchange) geht die Prüfungen der Reihe nach durch. Die häufigste Ursache auf Seiten von GitHub Actions ist ein nicht übereinstimmendes Format des `sub`-Claims (sein letztes Segment variiert zwischen `ref:...`-, `environment:...`- und `pull_request`-Ereignissen); der Verlaufseintrag zeigt dann den Grund `match_subject_prefix`.

## Einschränken, welche Workflows sich authentifizieren können

<Warning>
  Ein `subject_prefix` von `repo:your-org/*` allein passt auf jedes Repository in deiner Organisation, und ohne eine `ref`-Einschränkung passt es auch auf `pull_request`-Läufe, die von Forks ausgelöst werden. Jeder, der einen Pull Request gegen ein passendes Repository öffnen kann, könnte ein föderiertes Anthropic-Token erhalten.
</Warning>

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

* **Auf ein einzelnes Repository festlegen:** Verwende `subject_prefix: "repo:your-org/your-repo:*"`, damit andere Repositories in der Organisation nicht passen.
* **Auf einen geschützten Branch festlegen:** Füge `"ref": "refs/heads/main"` (oder deinen Release-Branch) unter `claims` hinzu, damit Pull-Request-Läufe und Feature-Branches nicht passen.
* **Den Owner explizit festlegen:** Füge `"repository_owner": "your-org"` unter `claims` als Defense-in-Depth-Prüfung gegen Grenzfälle beim Parsen von `sub` hinzu.
* **Auf ein Deployment-Environment festlegen:** Gleiche für Deploy-Jobs `subject_prefix: "repo:your-org/your-repo:environment:production"` ab und sichere dieses Environment in GitHub mit erforderlichen Reviewern ab.

## Nächste Schritte

* [Workload Identity Federation](https://platform.claude.com/docs/de/manage-claude/workload-identity-federation): vollständige Einrichtungsanleitung, Umgebungsvariablen und Rangfolge der Anmeldedaten.
* [Authentifizierung](https://platform.claude.com/docs/de/manage-claude/authentication): wie sich Föderation im Vergleich zu API-Keys verhält.
