Page history
gcp
manage-claude/wif-providers/gcp
History
manage-claude/wif-providers/gcp Changed · +1 / -1 lines
from line 429
| jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson' ``` -Check that `iss` is `https://accounts.google.com`, `aud` is `https://api.anthropic.com`, and `email` matches the value in your federation rule. Then run the exchange from the previous section. A successful exchange returns an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. On `400 invalid_grant`, see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common Google Cloud-side cause is the `email` claim missing (request the token with `format=full` so it is included). +Check that `iss` is `https://accounts.google.com`, `aud` is `https://api.anthropic.com`, and `email` matches the value in your federation rule. Then run the exchange from the previous section. A successful exchange returns an `access_token` beginning with `sk-ant-oat01-` and an `expires_in` value in seconds. If the exchange fails with the opaque `401` `authentication_error` response (message `Authentication failed`), check the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) for the deny reason and see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange); the most common Google Cloud-side cause is the `email` claim missing (request the token with `format=full` so it is included). ## Scope your rule
manage-claude/wif-providers/gcp First recorded · 450 lines, first recorded
## Prerequisites ## Configure Google Cloud ## Configure Anthropic ## Acquire and use the token ## Verify the setup ## Scope your rule ## Next steps
The first capture of this source. The page was already there, and this is what it said.
---
title: Use WIF with Google Cloud
url: https://platform.claude.com/docs/en/manage-claude/wif-providers/gcp
description: Federate Google Cloud workloads (Cloud Run, Cloud Functions, App Engine, GCE, GKE) to the Claude API using Google-signed identity tokens instead of static API keys.
---
Any Google Cloud compute environment with access to the instance metadata server (Cloud Run, Cloud Functions, App Engine, Compute Engine (GCE), and GKE with Workload Identity) can request a Google-signed identity token for its attached service account. The token's issuer is `https://accounts.google.com`, and Anthropic can validate it directly through standard OIDC discovery, with no extra Google Cloud configuration required.
This guide shows how to register the Google issuer with Anthropic, bind a Google service account to an Anthropic service account, and have your workload exchange its identity token for a short-lived Claude API access token.
## Prerequisites
* Familiarity with [WIF concepts](https://platform.claude.com/docs/en/manage-claude/workload-identity-federation#concepts): service accounts, federation issuers, and federation rules.
* A Google Cloud project with a workload running on Cloud Run, Cloud Functions, App Engine, Compute Engine, or GKE.
* A user-managed Google service account attached to that workload (not the Compute Engine default service account).
* Permission to create service accounts, federation issuers, and federation rules in the Claude Console for your Anthropic organization.
## Configure Google Cloud
Google issues identity tokens automatically to any workload with an attached service account. There is nothing to enable on the Google side beyond attaching the right service account, but the steps differ slightly between standard compute and GKE.
<Tabs>
<Tab title="Cloud Run, Cloud Functions, App Engine, GCE">
Attach a dedicated service account to your service or instance:
```bash CLI
gcloud run deploy my-service \
--service-account [email protected]
```
Inside the workload, the metadata server returns a signed identity token on demand. Request it with the `audience` you intend to register on the Anthropic side, and include `format=full` so the response carries the `email` claim:
```text wrap
GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full
Metadata-Flavor: Google
```
Or, with the gcloud CLI:
```bash CLI
gcloud auth print-identity-token \
--audiences="https://api.anthropic.com" \
--include-email
```
The SDK equivalents are shown in [Acquire and use the token](https://platform.claude.com/docs/en/manage-claude/wif-providers/gcp#acquire-and-use-the-token).
The decoded token payload looks like this:
```json
{
"iss": "https://accounts.google.com",
"aud": "https://api.anthropic.com",
"sub": "104892...",
"azp": "104892...",
"email": "[email protected]",
"email_verified": true,
"exp": 1775527120
}
```
The `sub` claim is the Google service account's opaque numeric unique ID. The `email` claim is the human-readable service account address. Match on both `sub` and `email` in your federation rule.
</Tab>
<Tab title="GKE with Workload Identity">
Enable [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) on your cluster and bind your Kubernetes service account to a Google service account with the `iam.gke.io/gcp-service-account` annotation:
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: inference-worker
namespace: prod
annotations:
iam.gke.io/gcp-service-account: [email protected]
```
With this binding in place, the GKE metadata server returns a Google-signed token identical to the Cloud Run and GCE case: same `https://accounts.google.com` issuer, same `email` claim, same fetch URL. Configure Anthropic exactly as in the next section.
A `format=full` token from GKE additionally includes `google.compute_engine.project_id`, `google.compute_engine.zone`, and `google.compute_engine.instance_name` claims, which you can reference in a federation rule's `condition` matcher (a CEL expression like `claims.google.compute_engine.project_id == "my-project"`) to scope access to a specific cluster or node pool.
<Note>
If you do not want to bind Kubernetes service accounts to Google service accounts, GKE pods can instead use the cluster's own OIDC issuer (`https://container.googleapis.com/v1/projects/PROJECT/locations/REGION/clusters/CLUSTER`) with a projected `serviceAccountToken` volume. That path uses a per-cluster issuer rather than `accounts.google.com`. See [Use WIF with Kubernetes](https://platform.claude.com/docs/en/manage-claude/wif-providers/kubernetes) for that pattern.
</Note>
</Tab>
</Tabs>
## Configure Anthropic
In the Claude Console, open **Settings → Workload identity**, click **Connect workload**, and select the **Google Cloud** 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](https://platform.claude.com/docs/en/manage-claude/wif-admin-api):
**Federation issuer:** Google publishes its OIDC discovery document publicly, so use discovery mode. This single issuer covers every Google Cloud surface (Cloud Run, GCE, Cloud Functions, App Engine, and GKE with Workload Identity). Differentiate workloads with rules, not issuers.
```json
{
"name": "gcp",
"issuer_url": "https://accounts.google.com",
"jwks": { "type": "discovery" }
}
```
**Federation rule:** Match on both the `sub` and `email` claims. `email` is the readable service-account address; `sub` is the service account's numeric unique ID, which Google never reuses, so pinning it protects the rule if the service account is deleted and a new one is later created with the same email. Find the unique ID with `gcloud iam service-accounts describe SA_EMAIL --format='value(uniqueId)'`.
```json
{
"name": "gcp-inference-worker",
"issuer_id": "fdis_...",
"match": {
"audience": "https://api.anthropic.com",
"claims": {
"sub": "104892101234567890123",
"email": "[email protected]"
}
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}
```
## Acquire and use the token
Inside your Google Cloud workload, fetch the identity token from the metadata server, exchange it at `POST /v1/oauth/token`, and use the returned bearer token to call the Claude API. Each Anthropic SDK handles the exchange and refresh loop for you when you supply a token-provider callable that returns a fresh identity token from the metadata server, as shown in the following examples.
<CodeGroup>
```bash cURL
# Fetch the Google-signed identity token from the metadata server
JWT=$(curl -sS -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full")
# Exchange it for an Anthropic access token
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)
# Call the Claude API
curl -sS 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",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello from Cloud Run"}]
}' | jq -r '.content[] | select(.type == "text") | .text'
```
```python Python
import os
import anthropic
import google.auth.transport.requests
import google.oauth2.id_token
from anthropic import WorkloadIdentityCredentials
AUDIENCE = "https://api.anthropic.com"
def fetch_google_identity_token() -> str:
request = google.auth.transport.requests.Request()
return google.oauth2.id_token.fetch_id_token(request, AUDIENCE)
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_google_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",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello from Cloud Run"}],
)
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";
const METADATA_URL =
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full";
async function fetchGoogleIdentityToken(): Promise<string> {
const response = await fetch(METADATA_URL, {
headers: { "Metadata-Flavor": "Google" }
});
return response.text();
}
const client = new Anthropic({
credentials: oidcFederationProvider({
identityTokenProvider: fetchGoogleIdentityToken,
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",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from Cloud Run" }]
});
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
```
```go Go
const audience = "https://api.anthropic.com"
googleIDToken := func(ctx context.Context) (string, error) {
creds, err := idtoken.NewCredentials(&idtoken.Options{Audience: audience})
if err != nil {
return "", err
}
tok, err := creds.Token(ctx)
if err != nil {
return "", err
}
return tok.Value, nil
}
client := anthropic.NewClient(
option.WithFederationTokenProvider(googleIDToken, 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,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello from Cloud Run")),
},
})
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
HttpClient http = HttpClient.newHttpClient();
HttpRequest metadataRequest = HttpRequest.newBuilder()
.uri(URI.create("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full"))
.header("Metadata-Flavor", "Google")
.build();
IdentityTokenProvider fetchGoogleIdentityToken = () -> {
try {
return http.send(metadataRequest, HttpResponse.BodyHandlers.ofString()).body();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
AnthropicClient client = AnthropicOkHttpClient.builder()
.federationTokenProvider(
fetchGoogleIdentityToken,
System.getenv("ANTHROPIC_FEDERATION_RULE_ID"),
System.getenv("ANTHROPIC_ORGANIZATION_ID"),
System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
.build();
Cut at 300 lines.