---
title: Usar WIF con AWS
url: https://platform.claude.com/docs/es/manage-claude/wif-providers/aws
description: Autentica cargas de trabajo de AWS en Lambda, EC2, ECS o EKS ante la Claude API con Workload Identity Federation y tokens de identidad emitidos por STS.
---

Las cargas de trabajo de AWS pueden autenticarse ante la Claude API sin claves de API estáticas intercambiando un token de identidad OIDC firmado por AWS. La ruta recomendada llama a la API [`GetWebIdentityToken`](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html) de AWS STS, que funciona en cualquier lugar donde la carga de trabajo tenga credenciales de AWS: Lambda, EC2, ECS y EKS. Las cargas de trabajo de EKS pueden usar alternativamente la [ruta de token proyectado de Kubernetes](https://platform.claude.com/docs/es/manage-claude/wif-providers/aws#use-eks-projected-service-account-tokens), que tiene menos pasos de configuración pero solo funciona dentro de un pod.

Esta guía muestra ambas rutas. Para los conceptos subyacentes (cuentas de servicio, emisores de federación y reglas de federación), consulta [Workload Identity Federation](https://platform.claude.com/docs/es/manage-claude/workload-identity-federation).

## Requisitos previos

* Familiaridad con los [conceptos de WIF](https://platform.claude.com/docs/es/manage-claude/workload-identity-federation#concepts): cuentas de servicio, emisores de federación y reglas de federación.
* Una carga de trabajo de AWS (pod de EKS, tarea de ECS, función Lambda o instancia de EC2) con un rol de IAM asociado.
* La CLI `aws` o un SDK de AWS disponible en la carga de trabajo.
* Permiso para crear cuentas de servicio, emisores de federación y reglas de federación en la Claude Console para tu organización de Anthropic.

## Usar tokens de identidad web de STS (recomendado)

La API `GetWebIdentityToken` de AWS STS devuelve un token OIDC firmado por AWS que afirma la identidad de IAM del llamador. Dado que usa las credenciales de AWS ambientales de la carga de trabajo, la misma integración cubre Lambda, EC2, ECS y EKS.

### Configurar AWS

<Steps>
  <Step title="Habilita la federación de identidad web saliente para la cuenta">
    Esta es una marca a nivel de cuenta, desactivada de forma predeterminada. En la consola de AWS, abre **IAM**, elige **Account settings** y habilita **Outbound web identity federation**. Para habilitarla programáticamente:

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

    Si esto no está habilitado, las llamadas a `GetWebIdentityToken` fallan con `OutboundWebIdentityFederationDisabledException`.
  </Step>

  <Step title="Otorga al rol de IAM de la carga de trabajo permiso para llamar a la API">
    Asocia esta política al rol de IAM con el que se ejecuta tu función Lambda, instancia de EC2 o tarea de ECS:

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

  <Step title="Encuentra la URL del emisor de STS de tu cuenta">
    Después de habilitar la federación saliente, la página **IAM > Account settings** muestra un campo **Get Token Issuer URL** con un valor de la forma `https://<uuid>.tokens.sts.global.api.aws`. Esta URL es única para tu cuenta de AWS; cópiala para el siguiente paso. Para obtenerla programáticamente:

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

### Configurar Anthropic

En la Claude Console, ve a la página [Workload identity](https://platform.claude.com/settings/workload-identity-federation) (`platform.claude.com/settings/workload-identity-federation`), haz clic en **Connect workload** y selecciona el mosaico **AWS**. El asistente te guía para registrar el emisor, crear una cuenta de servicio y crear una regla de federación.

El asistente crea estos recursos por ti. Usa los siguientes valores, ya sea que los ingreses en el asistente o los envíes a la [Admin API](https://platform.claude.com/docs/es/manage-claude/wif-admin-api):

**Emisor de federación:** Registra la URL del emisor de STS por cuenta que copiaste en el paso anterior. Expone un endpoint JWKS público, así que usa el modo de descubrimiento.

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

**Regla de federación:** Haz coincidir la audiencia que pasas a `GetWebIdentityToken` y el ARN del rol de IAM del rol llamador en el claim `sub`. El valor de `sub` es el ARN del rol de IAM de la carga de trabajo que llamó a la API, en la forma `arn:aws:iam::<account>:role/<role-name>`. El token también lleva un claim `https://sts.amazonaws.com/` con `aws_account`, `org_id`, `principal_id` y cualquier `request_tags` que hayas pasado; puedes hacer coincidencias sobre ellos con el mapa `claims` de la regla o una `condition` CEL para un control más fino.

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

Sé tan específico como la carga de trabajo lo permita. Haz coincidir el ARN exacto del rol, y solo amplía `subject_prefix` (por ejemplo, a `arn:aws:iam::123456789012:role/*`) si varios roles de IAM deben asignarse a la misma cuenta de servicio de Anthropic.

### Obtener y usar el token

Llama a `GetWebIdentityToken` con `https://api.anthropic.com` como audiencia y luego pasa el resultado a las credenciales de federación del SDK mediante `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`). Excepto en Java (consulta el comentario en ese ejemplo), esa opción recibe un proveedor de tokens, por lo que el SDK vuelve a invocar STS en cada actualización.

<Note>
  `GetWebIdentityToken` está disponible solo en endpoints regionales de STS. Si recibes `'STS' object has no attribute 'get_web_identity_token'` o un error similar, fija tu cliente de STS a una región (por ejemplo, `boto3.client("sts", region_name="us-east-1")`) y asegúrate de que tu SDK de AWS sea lo suficientemente reciente para incluir la API.
</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();

      // El SDK de Java recibe el token en sí o una ruta de archivo, no una función; con
      // source("value"), path(...) contiene el token. El SDK envía ese mismo token
      // en cada actualización del token de acceso de Anthropic, lo cual falla una vez que
      // ha expirado o, si tiene un claim jti, ya se ha usado. Para un proceso de mayor duración,
      // pasa source("file") y escribe un token nuevo en el archivo antes de
      // cada actualización.
      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,
  # ANTHROPIC_SERVICE_ACCOUNT_ID y ANTHROPIC_WORKSPACE_ID se leen del entorno
  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>

### Verificar la configuración

Desde dentro de la carga de trabajo, intercambia directamente un token emitido por STS e inspecciona la respuesta:

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

Un intercambio exitoso devuelve un `access_token` que comienza con `sk-ant-oat01-` y un valor `expires_in` en segundos. Si el intercambio falla con la respuesta opaca `401` `authentication_error` (mensaje `Authentication failed`), revisa la [página del historial de autenticación](https://platform.claude.com/settings/workload-identity-federation?tab=history) en la Claude Console para ver el motivo de denegación y consulta [Solucionar un intercambio fallido](https://platform.claude.com/docs/es/manage-claude/wif-reference#troubleshoot-a-failed-exchange); la causa más común del lado de AWS es una discrepancia en `iss` (la URL del emisor de STS por cuenta debe coincidir exactamente con el `issuer_url` registrado).

## Usar tokens de cuenta de servicio proyectados de EKS

Si tu carga de trabajo se ejecuta en un pod de EKS, puedes omitir la llamada a STS y leer un token de cuenta de servicio proyectado por Kubernetes directamente desde el disco. Kubernetes proyecta de forma nativa un token compatible con OIDC en el pod, y el SDK puede leerlo desde una ruta de archivo, por lo que no se requiere un callable proveedor de tokens. Esta ruta tiene dos pasos de configuración de AWS menos que la ruta de STS, pero solo funciona dentro de un pod; el mecanismo subyacente es el mismo que el de la [integración genérica de Kubernetes](https://platform.claude.com/docs/es/manage-claude/wif-providers/kubernetes).

Esta ruta requiere además un clúster de EKS con un [proveedor OIDC de IAM habilitado](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) y acceso `kubectl` al clúster.

### Configurar tu clúster de EKS

<Steps>
  <Step title="Encuentra la URL del emisor OIDC de tu clúster">
    Cada clúster de EKS tiene un emisor OIDC único. Obtenlo con la CLI de AWS:

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

    La salida se ve como `https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...`. Registrarás esta URL como emisor de federación en la siguiente sección.
  </Step>

  <Step title="Crea la cuenta de servicio y proyecta un token con audiencia de Anthropic">
    El webhook de identidad de pod de EKS detecta la anotación `eks.amazonaws.com/role-arn` y proyecta automáticamente un token con `aud: sts.amazonaws.com`, exponiendo su ruta como `AWS_WEB_IDENTITY_TOKEN_FILE`. Ese token es para la asunción de roles de AWS. Para el intercambio con Anthropic, proyecta un segundo token con `audience: https://api.anthropic.com` y móntalo en una ruta dedicada.

    ```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="Observa la forma de los claims del token">
    El token proyectado es un "JSON Web Token" (token web JSON), o JWT, firmado por el emisor OIDC de tu clúster. Su claim `sub` sigue la convención de Kubernetes `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
    }
    ```

    La proyección `serviceAccountToken` establece `aud` en `https://api.anthropic.com`. El token separado inyectado por IRSA en `AWS_WEB_IDENTITY_TOKEN_FILE` lleva `aud: sts.amazonaws.com` y es para llamadas a la API de AWS, no para este intercambio.
  </Step>
</Steps>

### Configurar Anthropic

En la Claude Console, ve a la página [Workload identity](https://platform.claude.com/settings/workload-identity-federation) (`platform.claude.com/settings/workload-identity-federation`), haz clic en **Connect workload** y selecciona el mosaico **AWS**. El asistente te guía para registrar el emisor, crear una cuenta de servicio y crear una regla de federación.

El asistente crea estos recursos por ti. Usa los siguientes valores, ya sea que los ingreses en el asistente o los envíes a la [Admin API](https://platform.claude.com/docs/es/manage-claude/wif-admin-api):

**Emisor de federación:** Los emisores de EKS exponen un endpoint JWKS público, así que usa el modo de descubrimiento. La URL del emisor debe coincidir exactamente con el claim `iss` del token. Registra un emisor por clúster.

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

**Regla de federación:** Haz coincidir el claim `sub` de Kubernetes y la audiencia de Anthropic `https://api.anthropic.com`. (Proyecta un token de cuenta de servicio dedicado con esa audiencia; no reutilices el token predeterminado de IRSA `sts.amazonaws.com`.)

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

Sé tan específico como la carga de trabajo lo permita. Relaja `subject_prefix` a `system:serviceaccount:inference:*` (el `*` final lo convierte en una coincidencia de prefijo) solo si todas las cuentas de servicio del namespace deben asignarse a la misma cuenta de servicio de Anthropic.

### Obtener y usar el token

Dentro del pod, el token proyectado está en `/var/run/secrets/anthropic.com/token` (expuesto como `ANTHROPIC_IDENTITY_TOKEN_FILE` en la especificación del Pod). Pasa ese archivo a las credenciales de federación del SDK y el SDK se encarga del intercambio y la actualización.

<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#
  // Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  // del entorno del pod.
  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
  # Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y 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;

  // Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y 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"

  # Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y 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>
  La especificación del Pod ya establece `ANTHROPIC_IDENTITY_TOKEN_FILE`, `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID` y `ANTHROPIC_WORKSPACE_ID`, por lo que puedes construir el cliente sin argumentos y el SDK lee las variables de entorno de federación automáticamente.
</Tip>

### Verificar la configuración

Desde dentro del pod, intercambia directamente el token proyectado e inspecciona la respuesta:

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

Un intercambio exitoso devuelve un `access_token` que comienza con `sk-ant-oat01-` y un valor `expires_in` en segundos. Si el intercambio falla con la respuesta opaca `401` `authentication_error` (mensaje `Authentication failed`), revisa la [página del historial de autenticación](https://platform.claude.com/settings/workload-identity-federation?tab=history) en la Claude Console para ver el motivo de denegación y consulta [Solucionar un intercambio fallido](https://platform.claude.com/docs/es/manage-claude/wif-reference#troubleshoot-a-failed-exchange); la causa más común del lado de EKS es que el `aud` del token proyectado no coincide con la regla (proyecta un token con `audience: https://api.anthropic.com`, no el predeterminado de IRSA `sts.amazonaws.com`).

## Delimitar el alcance de tu regla

<Warning>
  Un `subject_prefix` de `arn:aws:iam::123456789012:role/*` coincide con todos los roles de IAM de la cuenta. Cualquier principal que pueda asumir cualquier rol coincidente puede obtener un token federado de Anthropic.
</Warning>

Restringe el bloque `match` de la regla al alcance más estrecho que se ajuste a tu caso de uso:

* **Fija el ARN completo del rol:** Usa `subject_prefix: "arn:aws:iam::<account>:role/<role-name>"` sin `*` final para que otros roles de la cuenta no coincidan.
* **Fija el ID de la cuenta:** Haz coincidir el campo `aws_account` del claim `https://sts.amazonaws.com/` del token con el mapa `claims` o una `condition` CEL como verificación de defensa en profundidad contra un prefijo mal configurado.
* **Fija el namespace y la cuenta de servicio en EKS:** Usa el valor exacto `system:serviceaccount:<namespace>:<name>` sin `*` después del prefijo `system:serviceaccount:`.
* **Usa una regla separada por entorno:** Crea reglas distintas para las cargas de trabajo de producción, staging y desarrollo en lugar de ampliar un solo prefijo para cubrirlas todas.

## Próximos pasos

* Revisa la [referencia de WIF](https://platform.claude.com/docs/es/manage-claude/wif-reference) para la referencia completa de precedencia de credenciales, configuración de perfiles y coincidencia de reglas.
* Para clústeres de Kubernetes autogestionados que no están en EKS, consulta [Usar WIF con Kubernetes](https://platform.claude.com/docs/es/manage-claude/wif-providers/kubernetes).
