Authenticate to external services using an agent's own identity

Agents hosted on Google Cloud can use their own identity to authenticate to tools and services hosted on Google Cloud runtimes, such as Cloud Run or Google Kubernetes Engine (GKE), by requesting an OpenID Connect (OIDC) ID token from Agent Identity. Agents can also use these ID tokens to authenticate to third-party cloud platforms (such as Amazon Web Services (AWS) and Microsoft Azure), custom APIs, API gateways, and on-premises backends.

When an agent acts on its own authority to access an external service, Agent Identity issues an OpenID Connect (OIDC) ID token. This JSON Web Token (JWT) asserts the agent's SPIFFE identity and is signed by the issuer keys for the agent's trust domain (managed workload identity pool). External systems can verify these tokens without Google Cloud credentials or SDKs through public endpoints hosted by the Google Cloud Security Token Service:

  • An OpenID Connect Discovery 1.0 endpoint (/.well-known/openid-configuration) that publishes the OpenID provider metadata and the public key endpoint (jwks_uri).
  • A JSON Web Key Set (JWKS) endpoint (/openid/jwks) that serves the active public keys used to verify signatures on agent ID tokens.

Before you begin

  1. Verify that you have chosen the correct authentication method. Review how SPIFFE identities, trust domains, and agent credentials work in the Agent Identity overview.
  2. Create and deploy an agent with Agent Identity enabled.
  3. Ensure that your external service or identity provider meets the following requirements:
  4. Identify the following configuration values for your agent and target external service:
    • Issuer URL (iss claim): The workload identity pool issuer URL for your organization (https://sts.googleapis.com/v1/organizations/ORGANIZATION_ID/locations/global/workloadIdentityPools/TRUST_DOMAIN) or project (https://sts.googleapis.com/v1/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/TRUST_DOMAIN).
    • Allowed audience (aud claim): The audience URI that the external service or identity provider expects when validating ID tokens.
  5. Verify that you have the roles required to complete this task.

Required roles

To get the permissions that you need to deploy an agent with Agent Identity, ask your administrator to grant you the following IAM roles on your project:

  • Deploy an agent to Agent Runtime on Gemini Enterprise Agent Platform: Vertex AI User (roles/aiplatform.user)
  • Deploy an agent service to Cloud Run: Cloud Run Admin (roles/run.admin)

For more information about granting roles, see Manage access to projects, folders, and organizations.

These predefined roles contain the permissions required to deploy an agent with Agent Identity. To see the exact permissions that are required, expand the Required permissions section:

Required permissions

The following permissions are required to deploy an agent with Agent Identity:

  • Deploy an agent to Agent Runtime on Gemini Enterprise Agent Platform:
    • aiplatform.reasoningEngines.create
    • aiplatform.reasoningEngines.update
  • Deploy an agent service to Cloud Run:
    • run.services.create
    • run.services.update

You might also be able to get these permissions with custom roles or other predefined roles.

Obtain an OIDC ID token for an agent

To configure your agent to obtain and send an OIDC ID token to an external service, complete the following tasks:

  1. Configure your agent with Agent Identity
  2. Request an OIDC ID token in application code

Configure your agent with Agent Identity

Enable Agent Identity when you deploy your agent:

  • If you deploy your agent to Agent Runtime on Gemini Enterprise Agent Platform , set identity_type to AGENT_IDENTITY:

    remote_app = client.agent_engines.create(
        agent=app,
        config={
            "identity_type": types.IdentityType.AGENT_IDENTITY,
            "requirements": ["google-cloud-aiplatform[agent_engines,adk]"],
        },
    )
    
  • If you deploy a containerized agent service to Cloud Run, pass the --identity-type=agent-identity flag:

    gcloud run deploy SERVICE_NAME \
        --image=IMAGE_URL \
        --identity-type=agent-identity \
        --no-allow-unauthenticated

    Replace the following:

    • SERVICE_NAME: The name of your Cloud Run service.
    • IMAGE_URL: The container image URL for your agent.

Request an OIDC ID token in application code

In your agent's application code, use the Google Auth client library to request an OIDC ID token for your target external audience. The client library handles token generation, local caching, and automatic renewal from the metadata server.

By default, OIDC ID tokens issued for external audiences aren't bound to the runtime certificate.

The following example uses the google-auth library to request an OIDC ID token and attach it as a Bearer token in an outgoing request:

Python

from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import id_token

# 1. Specify the audience expected by the external receiver
# (for example, AWS Bedrock AgentCore or your external service URL).
target_audience = "https://EXTERNAL_SERVICE_AUDIENCE"

# 2. Create ID token credentials and an AuthorizedSession, which handles
# local token caching, automatic renewal before expiry, and the Bearer header.
credentials = id_token.fetch_id_token_credentials(audience=target_audience)
authed_session = AuthorizedSession(credentials)

# 3. Send the authenticated request to the external service.
response = authed_session.post(
    "https://EXTERNAL_SERVICE_ENDPOINT",
    json={"prompt": "Hello from Agent"},
)

Replace the following:

  • EXTERNAL_SERVICE_AUDIENCE: The audience URI expected by your receiving service (for example, bedrock.us-east-1.amazonaws.com or api.example.com).
  • EXTERNAL_SERVICE_ENDPOINT: The URL of the external API or backend endpoint that your agent calls.

For client library instructions and examples in other programming languages (including Go, Node.js, and Java), see Get an ID token. Current client library versions for these languages support --identity-type=agent-identity, but don't use bound tokens by default.

Verify Agent Identity ID tokens

When an external service receives an OIDC ID token from your agent, verify the token using either of the following approaches based on the target service:

Use built-in workload identity federation

If your receiving service runs on a cloud platform that supports built-in IAM authentication or OIDC workload identity federation, you don't need to write custom token verification code:

  • Cloud Run: If your receiving service runs on Cloud Run with authenticated ingress (--no-allow-unauthenticated), Cloud Run validates incoming Agent Identity tokens at the ingress layer. Grant the calling agent the Cloud Run Invoker (roles/run.invoker) role on the receiving service. For more information, see Authenticate to MCP servers on Cloud Run.

    If your service allows unauthenticated ingress and verifies tokens in application code, see Verify tokens programmatically.

  • Amazon Bedrock: Configure inbound JWT authentication by specifying the Google Cloud Security Token Service discovery URL or issuer URL and your expected audience. For instructions, see Configure inbound JWT authorizer in the AWS documentation.

  • Microsoft Entra ID: Configure a federated identity credential with the Other issuer scenario. Specify the Google Cloud Security Token Service issuer URL, the expected audience, and the subject identifier (sub claim). For instructions, see Create a trust relationship between an app and an external identity provider in the Microsoft Learn documentation.

Verify tokens programmatically

If your agent sends requests to a custom API, microservice, API gateway, or on-premises workload, your receiving service must verify the incoming OIDC ID token before granting access. Your agent typically passes this token in the Authorization: Bearer TOKEN HTTP header.

To verify incoming ID tokens programmatically, complete the following tasks:

  1. Extract and validate the token issuer URL
  2. Discover and cache the public signing keys
  3. Verify the token signature and claims
  4. Authorize the agent's SPIFFE identity

Extract and validate the token issuer URL

When an incoming request arrives, read the unverified JWT payload to extract the iss (issuer) claim. This claim contains the URL of the workload identity pool for the agent's trust domain. This URL serves as the base URL for the discovery document and public signing keys.

Before making any outgoing network requests, verify that the iss claim matches the expected Google Cloud Security Token Service workload identity pool URL for your organization or project:

  • Organization-level trust domains:

    https://sts.googleapis.com/v1/organizations/ORGANIZATION_ID/locations/global/workloadIdentityPools/TRUST_DOMAIN

    For example, for an organization with ID 123456789012, TRUST_DOMAIN is agents.global.org-123456789012.system.id.goog.

  • Project-level trust domains (for projects without an organization):

    https://sts.googleapis.com/v1/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/TRUST_DOMAIN

    For example, for a project with number 9876543210, TRUST_DOMAIN is agents.global.proj-9876543210.system.id.goog.

Discover and cache the public signing keys

After you validate the issuer URL, retrieve and cache the public signing keys from the Google Cloud Security Token Service:

  1. Query the OpenID Connect Discovery endpoint: Append /.well-known/openid-configuration to the base issuer URL and send an unauthenticated HTTP GET request:

    Before using any of the request data, make the following replacements:

    • ORGANIZATION_ID: Your Google Cloud organization ID. For a project without an organization, replace organizations/ORGANIZATION_ID with projects/PROJECT_NUMBER.
    • TRUST_DOMAIN: The workload identity pool ID for your agent's trust domain (for example, agents.global.org-123456789012.system.id.goog).

    HTTP method and URL:

    GET https://sts.googleapis.com/v1/organizations/ORGANIZATION_ID/locations/global/workloadIdentityPools/TRUST_DOMAIN/.well-known/openid-configuration

    To send your request, expand one of these options:

    A successful request returns an HTTP 200 OK status and a JSON object containing the OpenID Provider metadata, including the jwks_uri field:

    {
      "issuer": "https://sts.googleapis.com/v1/organizations/123456789012/locations/global/workloadIdentityPools/agents.global.org-123456789012.system.id.goog",
      "jwks_uri": "https://sts.googleapis.com/v1/organizations/123456789012/locations/global/workloadIdentityPools/agents.global.org-123456789012.system.id.goog/openid/jwks",
      "authorization_endpoint": "https://sts.googleapis.com/v1/organizations/123456789012/locations/global/workloadIdentityPools/agents.global.org-123456789012.system.id.goog/authorize",
      "token_endpoint": "https://sts.googleapis.com/v1/organizations/123456789012/locations/global/workloadIdentityPools/agents.global.org-123456789012.system.id.goog/token",
      "response_types_supported": [
        "id_token"
      ],
      "subject_types_supported": [
        "public"
      ],
      "id_token_signing_alg_values_supported": [
        "RS256"
      ]
    }
    
  2. Query the JSON Web Key Set (JWKS) endpoint: Send an unauthenticated HTTP GET request to the jwks_uri URL returned in the OpenID Provider metadata:

    Before using any of the request data, make the following replacements:

    • ORGANIZATION_ID: Your Google Cloud organization ID. For a project without an organization, replace organizations/ORGANIZATION_ID with projects/PROJECT_NUMBER.
    • TRUST_DOMAIN: The workload identity pool ID for your agent's trust domain (for example, agents.global.org-123456789012.system.id.goog).

    HTTP method and URL:

    GET https://sts.googleapis.com/v1/organizations/ORGANIZATION_ID/locations/global/workloadIdentityPools/TRUST_DOMAIN/openid/jwks

    To send your request, expand one of these options:

    A successful request returns an HTTP 200 OK status and a JSON object containing an array of public keys formatted according to RFC 7517:

    {
      "keys": [
        {
          "kty": "RSA",
          "use": "sig",
          "alg": "RS256",
          "kid": "4d1933f8e6c4e0b512c140989f6655c68997...",
          "n": "uQn4zN_1mQ0VpGv82-Wp3w...",
          "e": "AQAB"
        }
      ]
    }
    
  3. Cache the discovery document and keys: Responses from both the OpenID Connect Discovery endpoint and the JWKS endpoint include the following HTTP cache header:

    Cache-Control: public, max-age=86400, must-revalidate
    

    Cache the discovery document and JWKS for up to 24 hours (86400 seconds) to improve verification performance and avoid rate limiting.

    Google Cloud periodically rotates the private and public signing keys for workload identity pools. If your verifier receives an incoming token with a kid (key ID) that isn't in its local key cache, fetch a fresh JWKS from the /openid/jwks endpoint before rejecting the token.

    If you encounter HTTP errors when querying the discovery or JWKS endpoints, see Troubleshoot Agent Identity authentication issues.

Verify the token signature and claims

To cryptographically verify the token signature and validate the JWT claims, use a standard OIDC or JWT verification library (such as Google Tink) and do the following:

  1. Signature: Find the public key in the cached JWKS that matches the kid (key ID) in the JWT header. Validate the signature using the algorithm specified in the alg field (RS256). For forward compatibility, inspect the alg and kty fields in the JWKS dynamically rather than hardcoding algorithm types.
  2. Issuer (iss): Confirm that the iss claim matches the trusted Google Cloud workload identity pool issuer URL for your trust domain.
  3. Audience (aud): Confirm that the aud claim matches your service's configured audience identifier.
  4. Issued-at time (iat) and expiration time (exp): Verify that the iat claim is in the past and that the current time is earlier than the exp claim (allowing a small clock-skew tolerance, such as 1 to 2 minutes).

The following example uses Google Tink (tink.jwt) to verify an Agent Identity ID token against a JWKS JSON payload:

Python

import tink
from tink import jwt

# Initialize Tink JWT signature primitives (call once at application startup).
jwt.register_jwt_signature()


def verify_agent_identity_token(
    token: str,
    jwks_json: str,
    expected_issuer: str,
    expected_audience: str,
) -> jwt.VerifiedJwt:
    """Verifies an Agent Identity JWT against a JWKS JSON string using Tink.

    Args:
        token: The compact serialized JWT string.
        jwks_json: The JWKS JSON string fetched from the STS pool endpoint.
        expected_issuer: The expected token issuer ('iss' claim).
        expected_audience: The expected token audience ('aud' claim).

    Returns:
        jwt.VerifiedJwt: The verified JWT claims object.

    Raises:
        tink.TinkError: If the JWKS cannot be parsed, the key is not found,
            or token validation (signature, issuer, audience, expiration) fails.
    """
    # 1. Convert the JWKS JSON into a Tink public KeysetHandle.
    keyset_handle = jwt.jwk_set_to_public_keyset_handle(jwks_json)

    # 2. Instantiate the Tink JwtPublicKeyVerify primitive.
    jwt_verifier = keyset_handle.primitive(jwt.JwtPublicKeyVerify)

    # 3. Configure expected validation rules (issuer, audience, expiration).
    # Google Cloud STS sets 'typ': 'JWT' in the header, so
    # expected_type_header="JWT" is required.
    validator = jwt.new_validator(
        expected_issuer=expected_issuer,
        expected_audience=expected_audience,
        expected_type_header="JWT",
        allow_missing_expiration=False,
    )

    # 4. Cryptographically verify the signature and standard OIDC claims.
    return jwt_verifier.verify_and_decode(token, validator)

Authorize the agent's SPIFFE identity

After you verify the token's signature and standard claims, inspect the verified sub (subject) claim to authorize the request and record the calling agent in your audit logs.

The sub claim contains the agent's unique SPIFFE ID, for example:

  spiffe://agents.global.org-123456789012.system.id.goog/resources/aiplatform/projects/9876543210/locations/us-central1/reasoningEngines/my-test-agent

In your service's authorization logic, compare the verified sub claim against an allowlist of trusted agent SPIFFE IDs (or trust domain prefixes) before granting access to protected resources.

What's next