---
title: Usare WIF con GitHub Actions
url: https://platform.claude.com/docs/it/manage-claude/wif-providers/github-actions
description: Autentica i workflow di GitHub Actions verso la Claude API con token di identità a breve durata invece di chiavi API a lunga durata.
---

Ogni esecuzione di un workflow di GitHub Actions può richiedere un token di identità firmato dall'issuer ospitato di GitHub all'indirizzo `https://token.actions.githubusercontent.com`. Con la "Workload Identity Federation" (federazione delle identità dei carichi di lavoro), il tuo workflow scambia quel token con un token di accesso Anthropic a breve durata, così i tuoi job di CI possono chiamare la Claude API senza un secret `ANTHROPIC_API_KEY` memorizzato nel tuo repository.

Il claim `sub` del token codifica il repository e il contesto di attivazione. Per un push su un branch ha la forma `repo:<owner>/<repo>:ref:refs/heads/<branch>`. Le esecuzioni da pull request usano `repo:<owner>/<repo>:pull_request`, e i deployment vincolati a un environment usano `repo:<owner>/<repo>:environment:<name>`. La tua regola di federazione effettua il confronto con questo claim (e con altri, come `repository_owner` e `ref`) per decidere quali esecuzioni di workflow sono autorizzate ad autenticarsi.

## Prerequisiti

* Familiarità con i [concetti di WIF](https://platform.claude.com/docs/it/manage-claude/workload-identity-federation#concepts): service account, issuer di federazione e regole di federazione.
* Un repository GitHub in cui puoi modificare i file dei workflow e concedere il permesso `id-token: write`.
* Il permesso di creare service account, issuer di federazione e regole di federazione nella Claude Console per la tua organizzazione Anthropic.
* L'ID della tua organizzazione Anthropic. Puoi trovarlo nella pagina [Organization](https://platform.claude.com/settings/organization) (`platform.claude.com/settings/organization`) nella Claude Console.

## Configura il tuo workflow

GitHub emette un token di identità solo per i job che lo richiedono esplicitamente. Aggiungi il permesso `id-token: write` a livello di workflow o di job:

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

All'interno del job, il runner espone due variabili d'ambiente: `ACTIONS_ID_TOKEN_REQUEST_URL` e `ACTIONS_ID_TOKEN_REQUEST_TOKEN`. Chiama l'URL di richiesta con il token di richiesta come credenziale bearer e l'audience scelta come parametro di query, quindi scrivi il "JSON Web Token" (token web JSON), o JWT, restituito in un file:

```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
```

Se preferisci JavaScript, `actions/github-script` espone la stessa funzionalità tramite `core.getIDToken(audience)`:

```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);
```

Il token decodificato contiene claim che descrivono l'esecuzione del workflow. La tua regola di federazione effettua il confronto con questi:

```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"
}
```

Consulta il [riferimento di GitHub sul claim subject OIDC](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims) per l'elenco completo dei formati di `sub`.

## Configura Anthropic

Nella Claude Console, vai alla pagina [Workload identity](https://platform.claude.com/settings/workload-identity-federation) (`platform.claude.com/settings/workload-identity-federation`), fai clic su **Connect workload** e seleziona il riquadro **GitHub Actions**. La procedura guidata ti accompagna nella registrazione dell'issuer, nella creazione di un service account e nella creazione di una regola di federazione.

La procedura guidata crea queste risorse per te. Usa i seguenti valori sia che tu li inserisca nella procedura guidata sia che li invii all'[Admin API](https://platform.claude.com/docs/it/manage-claude/wif-admin-api):

**Issuer di federazione:** GitHub pubblica il proprio documento di discovery OIDC e il JWKS pubblicamente, quindi usa la modalità discovery. Anthropic aggiorna le chiavi automaticamente quando GitHub le ruota.

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

**Regola di federazione:** Fai corrispondere solo le esecuzioni di workflow di cui intendi fidarti. Consulta [Limita quali workflow possono autenticarsi](https://platform.claude.com/docs/it/manage-claude/wif-providers/github-actions#restrict-which-workflows-can-authenticate) per sapere come delimitare questi claim in modo sicuro.

```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
}
```

Sii specifico quanto il carico di lavoro lo consente. Allenta `subject_prefix` a `repo:your-org/your-repo:*` (abbinato a un vincolo `claims.ref`) solo se la regola deve corrispondere a più tipi di evento dallo stesso repository, perché il segmento finale di `sub` varia tra gli eventi `ref:...`, `environment:...` e `pull_request`.

## Acquisisci e usa un token

Imposta le variabili d'ambiente di federazione sul job e chiama l'SDK normalmente. `Anthropic()` (typescript: `new Anthropic()`; csharp: `new AnthropicClient()`; go: `anthropic.NewClient()`; java: `AnthropicOkHttpClient.fromEnv()`; php: `new Client()`; ruby: `Anthropic::Client.new`) legge `ANTHROPIC_IDENTITY_TOKEN_FILE`, scambia il JWT alla prima richiesta e aggiorna automaticamente il token di accesso prima che scada.

<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

  # Legge ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID e ANTHROPIC_IDENTITY_TOKEN_FILE
  # dall'ambiente del job.
  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";

  // Legge ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID e ANTHROPIC_IDENTITY_TOKEN_FILE
  // dall'ambiente del job.
  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
  // Legge ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID e ANTHROPIC_IDENTITY_TOKEN_FILE
  // dall'ambiente del job.
  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#
  // Legge ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID e ANTHROPIC_IDENTITY_TOKEN_FILE
  // dall'ambiente del job.
  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
  # Legge ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID e ANTHROPIC_IDENTITY_TOKEN_FILE
  # dall'ambiente del job.
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello, Claude"}'
  ```

  ```php PHP
  use Anthropic\Client;

  // Legge ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID e ANTHROPIC_IDENTITY_TOKEN_FILE
  // dall'ambiente del job.
  $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"

  # Legge ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID e ANTHROPIC_IDENTITY_TOKEN_FILE
  # dall'ambiente del job.
  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>

Ogni token di identità emesso da GitHub scade circa cinque minuti dopo l'emissione. L'endpoint di richiesta del token (`ACTIONS_ID_TOKEN_REQUEST_URL`) rimane valido per l'intera durata del job, quindi puoi recuperare un token nuovo in qualsiasi momento. L'SDK scambia il token al primo utilizzo e memorizza nella cache il token di accesso Anthropic risultante. Per i job che durano più a lungo della durata del token Anthropic, l'SDK rilegge `ANTHROPIC_IDENTITY_TOKEN_FILE` a ogni aggiornamento, quindi riesegui periodicamente il passaggio di recupero (o racchiudilo in un ciclo in background) per mantenere il file aggiornato. In alternativa, tranne che in Java, passa all'SDK un token provider che chiami direttamente `ACTIONS_ID_TOKEN_REQUEST_URL` invece di usare il percorso del file.

## Verifica la configurazione

Uno scambio riuscito restituisce un `access_token` che inizia con `sk-ant-oat01-` e un valore `expires_in` in secondi. Uno scambio negato restituisce un `401` `authentication_error` opaco con il messaggio fisso `Authentication failed`, indipendentemente da quale controllo sia fallito; nella maggior parte dei casi il motivo del rifiuto è registrato nella voce del tentativo nella [pagina della cronologia di autenticazione](https://platform.claude.com/settings/workload-identity-federation?tab=history) nella Claude Console, e [Risolvere i problemi di uno scambio fallito](https://platform.claude.com/docs/it/manage-claude/wif-reference#troubleshoot-a-failed-exchange) illustra i controlli in ordine. La causa più comune lato GitHub Actions è la mancata corrispondenza del formato del claim `sub` (il suo segmento finale varia tra eventi `ref:...`, `environment:...` e `pull_request`); la voce della cronologia mostra il motivo `match_subject_prefix`.

## Limita quali workflow possono autenticarsi

<Warning>
  Un `subject_prefix` pari a `repo:your-org/*` da solo corrisponde a ogni repository della tua organizzazione e, senza un vincolo `ref`, corrisponde anche alle esecuzioni `pull_request` attivate da fork. Chiunque possa aprire una pull request verso un repository corrispondente potrebbe ottenere un token Anthropic federato.
</Warning>

Vincola il blocco `match` della regola all'ambito più ristretto adatto al tuo caso d'uso:

* **Vincola a un singolo repository:** Usa `subject_prefix: "repo:your-org/your-repo:*"` in modo che gli altri repository dell'organizzazione non corrispondano.
* **Vincola a un branch protetto:** Aggiungi `"ref": "refs/heads/main"` (o il tuo branch di rilascio) sotto `claims` in modo che le esecuzioni da pull request e i feature branch non corrispondano.
* **Vincola esplicitamente l'owner:** Aggiungi `"repository_owner": "your-org"` sotto `claims` come controllo di difesa in profondità contro i casi limite nel parsing di `sub`.
* **Vincola a un environment di deployment:** Per i job di deploy, fai corrispondere `subject_prefix: "repo:your-org/your-repo:environment:production"` e proteggi quell'environment con revisori obbligatori in GitHub.

## Passaggi successivi

* [Workload Identity Federation](https://platform.claude.com/docs/it/manage-claude/workload-identity-federation): procedura completa di configurazione, variabili d'ambiente e precedenza delle credenziali.
* [Autenticazione](https://platform.claude.com/docs/it/manage-claude/authentication): come la federazione si confronta con le chiavi API.
