Add Workload Identity Federation (OIDC) authentication support (#1338)

* Add workload identity federation auth support

Adds anthropic_federation_rule_id, anthropic_organization_id,
anthropic_service_account_id, anthropic_workspace_id, and
anthropic_oidc_audience inputs. When the federation rule and organization
are set, the action fetches the workflow's GitHub Actions OIDC token,
writes it to a file in RUNNER_TEMP, keeps it refreshed during execution,
and points the Claude Code CLI at it via ANTHROPIC_IDENTITY_TOKEN_FILE so
the CLI can exchange it for a short-lived access token instead of using a
static API key.

* Add WIF example workflow and base-action federation docs

* Default workload identity OIDC audience to https://api.anthropic.com
This commit is contained in:
Ashwin Bhat
2026-05-21 15:19:15 -07:00
committed by GitHub
parent c9d66afb17
commit 661a6fefbd
12 changed files with 486 additions and 38 deletions
+18
View File
@@ -91,6 +91,24 @@ Add the following to your workflow file:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
```
### Workload Identity Federation
Instead of a static API key or OAuth token, you can authenticate via [Workload Identity Federation](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation) by setting the federation environment variables on the step. Fetch an OIDC identity token from your provider, write it to a file, and point the action at it:
```yaml
- name: Run Claude Code with workload identity federation
uses: anthropics/claude-code-base-action@beta
with:
prompt: "Your prompt here"
env:
ANTHROPIC_FEDERATION_RULE_ID: fdrl_xxxxxxxxxxxx
ANTHROPIC_ORGANIZATION_ID: 00000000-0000-0000-0000-000000000000
ANTHROPIC_SERVICE_ACCOUNT_ID: svac_xxxxxxxxxxxx
ANTHROPIC_IDENTITY_TOKEN_FILE: /path/to/identity-token
```
Note: the base action does not fetch or refresh the identity token itself — you are responsible for providing a valid token file. [`anthropics/claude-code-action`](https://github.com/anthropics/claude-code-action) handles fetching and refreshing the GitHub Actions OIDC token automatically via its `anthropic_federation_rule_id` input.
## Inputs
| Input | Description | Required | Default |
+18 -4
View File
@@ -8,6 +8,14 @@ export function validateEnvironmentVariables() {
const useFoundry = process.env.CLAUDE_CODE_USE_FOUNDRY === "1";
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
const claudeCodeOAuthToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
const federationRuleId = process.env.ANTHROPIC_FEDERATION_RULE_ID;
const federationOrganizationId = process.env.ANTHROPIC_ORGANIZATION_ID;
const hasWorkloadIdentity = Boolean(
federationRuleId && federationOrganizationId,
);
const hasPartialWorkloadIdentity =
!hasWorkloadIdentity &&
Boolean(federationRuleId || federationOrganizationId);
const errors: string[] = [];
@@ -20,10 +28,16 @@ export function validateEnvironmentVariables() {
}
if (!useBedrock && !useVertex && !useFoundry) {
if (!anthropicApiKey && !claudeCodeOAuthToken) {
errors.push(
"Either ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN is required when using direct Anthropic API.",
);
if (!anthropicApiKey && !claudeCodeOAuthToken && !hasWorkloadIdentity) {
if (hasPartialWorkloadIdentity) {
errors.push(
"Workload identity federation requires both ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID to be set.",
);
} else {
errors.push(
"Either ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, or workload identity federation (ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID) is required when using direct Anthropic API.",
);
}
}
} else if (useBedrock) {
const awsRegion = process.env.AWS_REGION;
+28 -1
View File
@@ -11,6 +11,8 @@ describe("validateEnvironmentVariables", () => {
originalEnv = { ...process.env };
// Clear relevant environment variables
delete process.env.ANTHROPIC_API_KEY;
delete process.env.ANTHROPIC_FEDERATION_RULE_ID;
delete process.env.ANTHROPIC_ORGANIZATION_ID;
delete process.env.CLAUDE_CODE_USE_BEDROCK;
delete process.env.CLAUDE_CODE_USE_VERTEX;
delete process.env.CLAUDE_CODE_USE_FOUNDRY;
@@ -42,7 +44,32 @@ describe("validateEnvironmentVariables", () => {
test("should fail when ANTHROPIC_API_KEY is missing", () => {
expect(() => validateEnvironmentVariables()).toThrow(
"Either ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN is required when using direct Anthropic API.",
"Either ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, or workload identity federation (ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID) is required when using direct Anthropic API.",
);
});
test("should pass when workload identity federation variables are provided", () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
expect(() => validateEnvironmentVariables()).not.toThrow();
});
test("should fail when only ANTHROPIC_FEDERATION_RULE_ID is provided", () => {
process.env.ANTHROPIC_FEDERATION_RULE_ID = "fdrl_test";
expect(() => validateEnvironmentVariables()).toThrow(
"Workload identity federation requires both ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID to be set.",
);
});
test("should fail when only ANTHROPIC_ORGANIZATION_ID is provided", () => {
process.env.ANTHROPIC_ORGANIZATION_ID =
"00000000-0000-0000-0000-000000000000";
expect(() => validateEnvironmentVariables()).toThrow(
"Workload identity federation requires both ANTHROPIC_FEDERATION_RULE_ID and ANTHROPIC_ORGANIZATION_ID to be set.",
);
});
});