Use WIF with GitHub Actions
Authenticate GitHub Actions workflows to the Claude API with short-lived identity tokens instead of long-lived API keys.
Every GitHub Actions workflow run can request a signed identity token from GitHub’s hosted issuer at https://token.actions.githubusercontent.com. With Workload Identity Federation (WIF), your workflow exchanges that token for a short-lived Anthropic access token. Your CI jobs can then call the Claude API without an ANTHROPIC_API_KEY secret stored in your repository.
The token’s sub claim encodes the repository and trigger context. For a push to a branch it has the form repo:<owner>/<repo>:ref:refs/heads/<branch>. Pull-request runs use repo:<owner>/<repo>:pull_request, and environment-gated deployments use repo:<owner>/<repo>:environment:<name>. Your federation rule matches against this claim (and others, such as repository_owner and ref) to determine which workflow runs can authenticate.
Prerequisites
- Familiarity with WIF concepts: service accounts, federation issuers, and federation rules.
- A GitHub repository where you can edit workflow files and grant the
id-token: writepermission. - Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
- Your Anthropic organization ID. You can find it on the Organization page (
platform.claude.com/settings/organization) in the Claude Console.
Configure your workflow
GitHub only issues an identity token to jobs that explicitly request it. Add the id-token: write permission at the workflow or job level:
permissions:
id-token: write
contents: readInside the job, the runner exposes two environment variables: ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN. Call the request URL with the request token as a bearer credential and your chosen audience as a query parameter. Then write the returned JSON Web Token (JWT) to a file:
- 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-jwtIf you prefer JavaScript, actions/github-script exposes the same capability through core.getIDToken(audience):
- 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);The decoded token carries claims that describe the workflow run. Your federation rule matches against these:
{
"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"
}See GitHub’s OIDC subject claim reference for the full list of sub formats.
Configure Anthropic
In the Claude Console, go to the Workload identity page (platform.claude.com/settings/workload-identity-federation), click Connect workload, and select the GitHub Actions tile. The wizard walks you through registering the issuer, creating a service account, and creating a federation rule.
The wizard creates these resources for you. Use the following values whether you enter them in the wizard or send them to the Admin API:
Federation issuer: GitHub publishes its OIDC discovery document and JWKS publicly, so use discovery mode. Anthropic refreshes the keys automatically when GitHub rotates them.
{
"name": "github-actions",
"issuer_url": "https://token.actions.githubusercontent.com",
"jwks": { "type": "discovery" }
}Federation rule: Match only the workflow runs you intend to trust. See Restrict which workflows can authenticate for how to scope these claims safely.
{
"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
}Be as specific as the workload allows. Loosen subject_prefix to repo:your-org/your-repo:* (paired with a claims.ref constraint) only if the rule must match multiple event types from the same repository. The trailing segment of sub varies among ref:..., environment:..., and pull_request events.
Acquire and use a token
Set the federation environment variables on the job, and write GitHub’s OIDC token to the file that ANTHROPIC_IDENTITY_TOKEN_FILE names:
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.pyThen call the Claude API from your script. An SDK client that you create without credentials reads ANTHROPIC_IDENTITY_TOKEN_FILE, exchanges the JWT on the first request, and refreshes the access token automatically before it expires.
import anthropic
# Reads ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
# ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID, and ANTHROPIC_IDENTITY_TOKEN_FILE
# from the job environment.
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"))Each GitHub-issued identity token expires roughly 5 minutes after issuance. The token-request endpoint (ACTIONS_ID_TOKEN_REQUEST_URL) stays valid for the entire job, so you can fetch a fresh token at any point. The SDK exchanges the token on first use and caches the resulting Anthropic access token. On each refresh, the SDK rereads ANTHROPIC_IDENTITY_TOKEN_FILE. For jobs that run longer than the Anthropic token’s lifetime, rerun the fetch step periodically (or wrap it in a background loop) to keep the file current. Alternatively, except in Java, pass the SDK a token provider that calls ACTIONS_ID_TOKEN_REQUEST_URL directly instead of using the file path.
Verify the setup
A successful exchange returns an access_token starting with sk-ant-oat01- and an expires_in value in seconds. A denied exchange returns an opaque 401 authentication_error with the fixed message Authentication failed, whichever check failed. In most cases, the attempt’s entry on the authentication history page in the Claude Console shows the deny reason. Troubleshoot a failed exchange walks through the checks in order. The most common GitHub Actions-side cause is a sub claim format that doesn’t match, because its trailing segment varies among ref:..., environment:..., and pull_request events. The history entry then shows the reason match_subject_prefix.
Restrict which workflows can authenticate
Lock the rule’s match block to the narrowest scope that fits your use case:
- Pin to a single repository: Use
subject_prefix: "repo:your-org/your-repo:*"so other repositories in the organization do not match. - Pin to a protected branch: Add
"ref": "refs/heads/main"(or your release branch) underclaimsso pull-request runs and feature branches do not match. - Pin the owner explicitly: Add
"repository_owner": "your-org"underclaimsas a defense-in-depth check againstsubparsing edge cases. - Pin to a deployment environment: For deploy jobs, match
subject_prefix: "repo:your-org/your-repo:environment:production"and gate that environment with required reviewers in GitHub.
Next steps
- Workload Identity Federation: full setup walkthrough, environment variables, and credential precedence.
- Authentication: how federation compares to API keys.
Was this page helpful?