Workload identity for AI coding agentsStop shipping a static API key
Your coding agent authenticates with a long-lived API key sitting in an env var or a CI secret. Workload Identity Federation replaces that key with a token the agent mints at runtime from an identity it already has, and the token expires in an hour. The key never exists.
-
01
Identity
OIDC JWT
The platform signs a workload identity token
ambient · no secret -
02
Exchange
/v1/oauth/token
JWT swapped for a Claude token
RFC 7523 grant -
03
Token
sk-ant-oat01
Expires in an hour, refreshed by the SDK
60–86,400s
TL;DR· the answer, in twenty seconds
What: Anthropic shipped Workload Identity Federation for the Claude API. An agent presents a signed OIDC token from an identity provider you already run (GitHub Actions, Kubernetes, AWS, GCP, Okta), exchanges it at POST /v1/oauth/token, and gets a short-lived sk-ant-oat01-... token. There is no sk-ant-api... key to store, rotate, or leak.
The minimum fix: if your agent runs in CI or on a managed host, register a federation issuer and rule, point the SDK at the rule, and unset ANTHROPIC_API_KEY. The token lifetime is configurable from 60 to 86,400 seconds, default 3600.
The lesson: federation removes the long-lived model key from disk. It does not touch the database password, the Stripe key, or anything else the agent reads at runtime. Apply the same short-lived, scoped, audited pattern to those, or you have hardened one credential and left the rest.
Anthropic added Workload Identity Federation to the Claude API. The short version: your coding agent can authenticate without an sk-ant-api... key anywhere in its environment. It presents a token it already has, gets a short-lived Claude token in return, and the long-lived secret never exists.
This matters because the static API key is the credential nobody wants to think about. It lives in a CI secret, a .env file, a shell profile, a Kubernetes Secret. It works for a year. It shows up in printenv, in a crash dump, in a tool call the agent makes to a hostile MCP server. Every guide about keeping it safe is really a guide about managing the blast radius of a thing that should not have a long life in the first place.
Federation is the version where you stop managing the key and start not having one. It is worth understanding the mechanism, because the same pattern applies to far more than the model API.
What changes in 60 seconds
- The agent never holds a long-lived
sk-ant-api...key. It holds an identity, and exchanges that identity for a token when it needs one. - The identity comes from the platform the agent already runs on: a GitHub Actions OIDC token, a Kubernetes projected service-account token, an AWS or GCP metadata token, an Okta or Entra assertion.
- The exchange happens at
POST /v1/oauth/tokenusing the OAuth 2.0jwt-bearergrant from RFC 7523. You get back a token that expires in minutes to hours. - The SDK runs the exchange and the refresh for you. Your code constructs a client with no
api_keyand calls the API as usual. - The thing you now protect is the identity provider, not a string. That is a better thing to protect, and it is not nothing.
The key is the problem, not the place you hide it
Most secrets advice for coding agents is about location. Move the key out of .env. Put it in a vault. Inject it at runtime instead of committing it. Rotate it every 90 days. All of that reduces exposure. None of it changes the fact that a credential with a long life is sitting somewhere, and anything that can read the agent's environment can read it.
A static key is a bearer token with no expiry. Whoever holds the bytes is the principal. That is why a leaked key is an incident and not an inconvenience: there is no clock running against the attacker. They use it until you notice and revoke, and "until you notice" is the part you do not control.
Rotation is the usual answer, and rotation is a treadmill. You rotate on a schedule the attacker does not care about. A key leaked an hour after rotation is good for almost the full window. The rotation interval is a guess about how fast you will detect a compromise, encoded as a policy, applied to every key whether or not it ever leaks.
Federation changes the question from "where do I keep the key and how often do I change it" to "what is this workload, and is it allowed to act as this service account right now." The credential the agent presents is short-lived by construction. The credential it receives is short-lived by construction. There is no long-lived secret in the loop to leak.
How federation actually works
Three things happen at runtime.
First, the identity provider issues a JWT to the workload. On most platforms this is ambient. A Kubernetes pod gets a projected service-account token mounted into the filesystem. A GitHub Actions job requests an OIDC token from the Actions endpoint. AWS hands out an STS web identity token. The agent does not store this token. The platform mints it, scoped to the workload, and rotates it well before it expires.
Second, the SDK exchanges that JWT for an Anthropic token. It posts the JWT to POST /v1/oauth/token with the urn:ietf:params:oauth:grant-type:jwt-bearer grant. Anthropic verifies the signature against the JWKS you registered for the issuer, checks the standard exp, nbf, and iat claims, and matches the JWT's claims against a federation rule you configured. If it matches, you get a standard OAuth token response with a short-lived sk-ant-oat01-... token bound to a service account.
Third, the SDK sends that token on every request and refreshes it before it expires. Your application constructs the client with no key:
from anthropic import Anthropic, WorkloadIdentityCredentials, IdentityTokenFile
client = Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=IdentityTokenFile(
"/var/run/secrets/anthropic.com/token"
),
federation_rule_id="fdrl_...",
organization_id="00000000-0000-0000-0000-000000000000",
service_account_id="svac_...",
workspace_id="wrkspc_...",
),
)
The raw exchange, for a shell script or a language without an SDK, is one HTTP call:
JWT=$(cat /var/run/secrets/anthropic.com/token)
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_..."
}'
You configure three resources once, in the Claude Console, before any of this works. A service account (svac_...) is the non-human identity the minted token acts as. A federation issuer (fdis_...) registers an OIDC provider and tells Anthropic which JWTs to trust. A federation rule (fdrl_...) is the bridge: when a JWT from this issuer has claims that look like this, mint a token for that service account, with this scope and this lifetime. The lifetime is token_lifetime_seconds, configurable from 60 to 86,400, default 3600.
The distinction the docs draw is the one that matters: an API key is a credential, while a service account has credentials minted for it on demand. You can audit which workload acted as which service account. You cannot audit a string.
Where the agent's identity comes from
The reason this works without a secret is that the agent already has an identity. It just was not using it to talk to the model API.
A GitHub Actions job already proves who it is to cloud providers with OIDC. The token's claims say which repository, which workflow, which branch. A Kubernetes workload already has a projected service-account token with a sub like system:serviceaccount:prod:agent-runner. An AWS workload has an STS identity. A SPIFFE deployment hands every workload a JWT-SVID. These are the identities your infrastructure already uses to make authorization decisions. Federation lets the Claude API consume the same proof.
That is the conceptual move. Instead of provisioning a new secret for every place an agent runs, you reuse the identity the place already has. The federation rule can match on a subject prefix, an audience, exact claim values, or a CEL expression for anything more complex. A rule that matches system:serviceaccount:prod:agent-runner grants the model API to exactly that workload and nothing else in the cluster.
This is the same shape as cloud workload identity, applied to a model provider. If you have used IRSA on EKS or Workload Identity on GKE, you already know the pattern: the workload presents a platform-signed token, a trust policy decides what it maps to, and a short-lived credential comes back. Anthropic's version is that pattern with the Claude API as the resource.
The case that lands closest to a coding agent is CI. A claude-code-action run in GitHub Actions, or any agent you trigger from a pipeline, already has an OIDC token available with no key stored in the repo or the org secrets. The setup docs for the action treat keyless auth as a first-class path: request the Actions OIDC token, map it through a federation rule scoped to your repository and workflow, and the job calls Claude with nothing static checked in anywhere. The same job that would have read a repository secret named ANTHROPIC_API_KEY now reads an identity the runner mints fresh for that run and throws away when it ends.
Federation moves the trust, it does not delete it
Here is the part the launch posts skip.
Federation removes the static key. It does not remove the need for trust. It relocates trust to the identity provider that signs the JWT. The Anthropic docs say this directly: federated authentication is only as strong as the upstream provider. If an attacker can make your IdP sign a token with the right claims, they can mint Claude tokens without ever touching your infrastructure. A misconfigured federation rule that matches too broadly, an issuer with a weak sub claim, a CI provider that lets a fork request OIDC tokens: each of these is the new version of a leaked key. The crown jewel changed shape. It did not disappear.
There is a precedence footgun worth knowing before you trust the migration. In every SDK, ANTHROPIC_API_KEY sits above federation in the credential resolution order. A leftover key in the environment silently shadows the whole federation setup. You think you migrated. You did not. The fix is to confirm the key is unset everywhere the workload runs, then check which source actually won. Skip that and you ship a config that looks federated and runs on the old key.
The default scope is workspace:developer, the same access an API key for that workspace would have. Federation makes the credential short-lived. It does not make it narrow. A minted token can do what an API key could do, for the workspace it targets. Short lifetime shrinks the time window. It does not shrink the capability.
The largest gap is the one outside the model API entirely. Your coding agent does not only call Claude. It reads a database URL, an AWS profile, a Stripe key, a GitHub token, half a dozen third-party API secrets. None of those are federated. Workload Identity Federation is a clean answer for one credential, the one that talks to the model. The agent's actual secret exposure is the pile of other keys it loads to do work, and that pile is untouched by anything in this article. Federating the model key and leaving a plaintext DATABASE_URL in the same environment is real progress on the wrong square of the board.
A checklist before you call it done
[ ] Register a federation issuer for each environment (prod cluster, CI, staging are separate issuers)
[ ] Create a service account; do NOT reuse one across trust boundaries
[ ] Write a federation rule that matches the narrowest claim your IdP exposes (subject prefix or CEL)
[ ] Set token_lifetime_seconds to the shortest value your job tolerates, not the 3600 default
[ ] Point the SDK at the rule; construct the client with no api_key
[ ] Unset ANTHROPIC_API_KEY in container env, CI secrets, and shell profiles
[ ] Verify which credential won (ant auth status) before deleting the old key
[ ] Revoke the old API key in the Console once federation is confirmed live
[ ] Inventory every OTHER secret the agent loads; federation does not cover them
[ ] Confirm your IdP's OIDC config rejects tokens from forks / untrusted callers
What this means for your stack
If your agent runs anywhere with an ambient identity, which is most CI and most managed infrastructure, the minimum action is to federate the model API credential and delete the static key. Set the token lifetime to the shortest value your jobs tolerate. Match the federation rule to the narrowest claim your provider exposes. Then go count the other secrets the agent still loads, because that count is the real exposure.
The pattern underneath federation is older than this feature and applies to all of those other secrets: do not hand a process a long-lived credential and trust it to behave. Give it a short-lived, scoped, audited grant at the moment it needs one, then take the grant back. Anthropic built that for the Claude API. Nothing built it for your database password by default.
hasp is one working implementation of that pattern for the secrets federation does not cover. curl -fsSL https://gethasp.com/install.sh | sh, hasp setup, then hand the next agent session a reference and a time-boxed grant instead of a key. Source-available (FCL-1.0), local-first, macOS and Linux, no account.
The test for any agent credential is the same whether it is the model key or anything else: if it leaked right now, how long is it useful, and what can it touch. Federation gives a good answer for one credential. The job is to give the same answer for all of them.
Sources· cited above, in one place
- Anthropic Workload Identity Federation for the Claude API
- RFC 7523 JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants
- GitHub OIDC Configuring OpenID Connect in cloud providers
- SPIFFE / SPIRE Workload identity
- Anthropic Security advisories and Claude Code release notes
- Fair Core License FCL-1.0-ALv2 text
Stop handing the agent your real keys.
hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.
- Local, encrypted vault — no account, no cloud, no telemetry by default.
- Brokered run — agent gets a reference, the child process gets the value.
- Pre-commit + pre-push hooks catch managed values before they ship.
- Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.