Skip to content

A2A authentication

This guide covers how to configure authentication for Agent-to-Agent (A2A) communication. There are two supported authentication methods:

  1. DataRobot API key — bearer token auth for DataRobot-hosted agents.
  2. Okta cross-application access (XAA) — two-step token exchange for federated Okta environments (hybrid RFC 8693 / RFC 7523 flow).

Both methods use the authenticated_a2a_client function group on the client side and the cross_application_access server-side config to publish requirements in the agent card.

Installation

# With Okta XAA support (adds okta-client-python)
pip install "datarobot-genai[dragent,langgraph,auth]>=0.15.40"

Replace langgraph with crewai or llamaindex depending on the target framework.

Option 1: DataRobot API key authentication

Use this when calling a DataRobot-hosted A2A agent that accepts a standard DataRobot API token.

Environment variables

Variable Description
DATAROBOT_API_TOKEN DataRobot API token.

The token is loaded automatically — no need to put it in workflow.yaml.

workflow.yaml

general:
  front_end:
    _type: dragent_fastapi
    step_adaptor:
      mode: 'off'
    a2a:
      server:
        name: My Agent
        description: A helpful assistant agent.

function_groups:
  remote_agent:
    _type: authenticated_a2a_client
    url: "https://app.datarobot.com/api/v2/deployments/<deployment-id>/directAccess/a2a/"
    auth_provider: datarobot_auth

llms:
  datarobot_llm:
    _type: datarobot-llm-component

workflow:
  _type: langgraph_agent
  llm_name: datarobot_llm
  description: My agent
  tool_names:
    - remote_agent

authentication:
  datarobot_auth:
    _type: datarobot_api_key

How it works

  1. On each A2A call, the datarobot_api_key auth provider injects the DATAROBOT_API_TOKEN as an Authorization: Bearer <token> header.
  2. The remote agent's A2A endpoint validates the token against the DataRobot platform.

This is the simplest setup — no agent card extensions or multi-step flows involved.

Important: datarobot_api_key is the default authentication mechanism for DataRobot-hosted agents. Every agent card includes securitySchemes; agents without explicit OAuth configuration advertise HTTP Bearer auth (bearerAuth) for DataRobot API tokens. When the remote agent card declares OAuth2 via cross_application_access, security-scheme negotiation validates and requires a matching auth provider on the client side. Use okta_cross_app_access (Option 2) for OAuth2-protected agents.

Option 2: Okta cross-application access (XAA)

Use this when calling an agent protected by Okta's federated identity model. The flow obtains a scoped access token through a two-step exchange.

Prerequisites

  • An Okta organization with Cross-Application Access enabled.
  • A registered AI agent principal in Okta with a private key pair.
  • The auth extra installed (pip install "datarobot-genai[auth]").

Environment variables

Variable Description
IDP_AGENT_ID Okta AI agent principal ID (used as iss/sub in JWT client assertions).
IDP_AGENT_PRIVATE_KEY_JWK Base64-encoded or raw-JSON private JWK (signs JWT client assertions).

Both are loaded automatically from env vars, .env, DataRobot Runtime Parameters, or file_secrets.

Full workflow.yaml example

general:
  front_end:
    _type: dragent_fastapi
    step_adaptor:
      mode: 'off'
    a2a:
      server:
        name: Blog Content Writer
        description: >-
          An AI content writing agent that researches and writes
          well-structured blog posts.
      skills:
        - id: write_blog
          name: Write Blog Post
          description: Researches and writes a blog post on the given topic.
          tags: []
          examples:
            - Write a blog post about the future of AI in healthcare
            - Create an article about sustainable energy trends

      # Opt in to enforcing the inbound token's claims (all API routes, not just /a2a).
      # When enabled, a token's aud claim must match
      # cross_application_access.token_request.audience
      oauth_claim_validation: true

      # Server-side: advertise XAA requirements in the agent card
      cross_application_access:
        # Step 1: Token exchange to fetch the ID-JAG (RFC 8693)
        token_exchange:
          trusted_issuer: "https://your-org.okta.com"
          audience: "https://your-org.okta.com/oauth2/ausXXXXXXXXXXXXXXX"
        # Step 2: Execute the Final Grant (RFC 7523)
        token_request:
          token_url: "https://your-org.okta.com/oauth2/ausXXXXXXXXXXXXXXX/v1/token"
          audience: "https://app.datarobot.com/<org_id>/<agent_id>"
          scopes:
            - "blog:write"

      # Optional: external identity and URL overrides published on the agent card
      external:
        id: "my-agent-id"       # Emitted as urn:datarobot:agent:identity:external
        url: "https://my-agent-id.example.com/a2a/"  # Overrides the auto-generated card URL

      # Optional: per-agent opt-in for unauthenticated agent-card access
      # (also requires platform-level opt-in per cluster)
      enable_unauthenticated_well_known_route: true

      # Optional: options shaping the card served to unauthenticated callers
      redacted_agent_card:
        # Advertise skills on the redacted card (has no effect unless
        # enable_unauthenticated_well_known_route is also true)
        enable_skills: true

# Client-side: call a remote XAA-protected agent
function_groups:
  remote_agent:
    _type: authenticated_a2a_client
    url: "https://app.datarobot.com/api/v2/deployments/<deployment-id>/directAccess/a2a/"
    auth_provider: okta_auth

llms:
  datarobot_llm:
    _type: datarobot-llm-component

workflow:
  _type: langgraph_agent
  llm_name: datarobot_llm
  description: LangGraph planner/writer agent
  tool_names:
    - remote_agent

authentication:
  okta_auth:
    _type: okta_cross_app_access

How it works

The XAA flow operates in two steps:

  1. Token Exchange (RFC 8693) — The caller's incoming Okta access token (from x-datarobot-external-access-token) is exchanged for an ID-JAG (Identity Assertion Authorization Grant) via the org-level Authorization Server (token_exchange.trusted_issuer).

  2. JWT Bearer Grant (RFC 7523) — The ID-JAG is exchanged for a scoped access token at the resource AS token endpoint (token_request.token_url), granting access to the target agent with the requested scopes.

Both steps authenticate the client using the same method (private key jwt), signing JWT client assertions with the private key from IDP_AGENT_PRIVATE_KEY_JWK.

With a central registry peer (registry), request-time agent card lookups try the caller's Okta token first and fall back to DATAROBOT_API_TOKEN. See Registry lookup credentials.

Server-side configuration reference: cross_application_access

These fields are declared in the serving agent's workflow.yaml and published on the A2A agent card.

Field Required Default Purpose
token_exchange.trusted_issuer Yes — Org-level Authorization Server issuer URL.
token_exchange.audience Yes — Resource AS base URL (where ID-JAG is fetched from).
token_request.token_url Yes — Token endpoint of the resource AS.
token_request.audience Yes — Final resource identifier for the agent. Advertised on the agent card and enforced on inbound requests — see Inbound audience validation.
token_request.scopes No ["read_data"] Scopes the caller must request.

Note: grant_type URNs are injected automatically by the generator — do not set them in workflow.yaml. The agent card will always contain urn:ietf:params:oauth:grant-type:token-exchange (Step 1) and urn:ietf:params:oauth:grant-type:jwt-bearer (Step 2).

Inbound audience validation

The serving agent enforces token_request.audience, not just publishes it. The gateway validates an incoming JWT and forwards it as-is, so without this a token minted for a different agent would be accepted and exchanged here. It is an audience check, not an authentication check: a caller with no IdP token — using a DataRobot API token, Option 1: DataRobot API key authentication — passes through.

Condition Result
aud contains token_request.audience Proceeds
aud does not 401 {"detail": "Authorization audience claim validation failed"}
Present but not a decodable JWT 422 {"detail": "Malformed authorization token: ..."}
No IdP token Proceeds

The token is read from x-datarobot-external-access-token only (bare or Bearer-prefixed). authorization is never read as an IdP-token carrier — it carries DataRobot's own credentials instead, some of which are JWTs too.

  • Applies to every route, not just /a2a.
  • Signature, issuer and expiry are not re-verified — the gateway owns that.
  • No route is exempt, agent-card discovery included. Auth there is optional, so an unauthenticated request still reaches the handler and enable_unauthenticated_well_known_route decides whether it sees a redacted card or a generic 404; a request that does carry a token is validated first, and one naming another agent gets 401 instead of a card.
  • Off unless a2a.oauth_claim_validation: true is set. With the flag on but no cross_application_access.token_request.audience to enforce, startup fails rather than pretending to. Either state is logged at startup.
  • Audience comparison is exact — no case or trailing-slash leniency.

Server-side configuration reference: external

Optional fields under general.frontend.a2a.external that control additional identity metadata and the agent card URL.

Field Purpose
external.id Catalog discovery identifier. Emitted as the urn:datarobot:agent:identity:external extension on the agent card.
external.url Overrides the auto-generated agent card endpoint URL. Used as-is — no normalization is applied.

Server-side configuration reference: enable_unauthenticated_well_known_route

Unauthenticated access to GET /.well-known/agent-card.json requires two independent opt-ins:

  1. Platform (per cluster) — cluster administrators enable routing of unauthenticated traffic to agents. This is configured outside workflow.yaml.
  2. Agent (per deployment) — set enable_unauthenticated_well_known_route: true under general.front_end.a2a in the agent's workflow.yaml.

Both must be enabled for anonymous callers to reach the agent card endpoint. The agent-side flag is enforced by the A2A server in this library; platform routing is enforced before the request reaches the agent process.

When the agent-side flag is off, an anonymous request gets the same generic 404 {"detail": "Not Found"} as a request for an agent that does not exist — never a 401, and never a body naming the flag. A distinguishable refusal is an existence oracle: it would let an anonymous scanner enumerate live agents by status code alone and tell it which knob unlocks the card. The reason is logged server-side instead, so the refusal stays debuggable from the agent's own logs.

Field Default Purpose
oauth_claim_validation false Opt in to enforcing the inbound token's claims — aud today, scope later — on every route, not just /a2a. The value enforced comes from cross_application_access.token_request.audience. Not published on the agent card.
enable_unauthenticated_well_known_route false Per-agent developer opt-in. When true, unauthenticated requests that reach the agent receive a redacted agent card. When false, they receive the generic 404 {"detail": "Not Found"} — indistinguishable from a nonexistent agent, so the refusal reveals nothing. Authenticated callers always receive the full card regardless of this setting.

Server-side configuration reference: redacted_agent_card

Fields under general.front_end.a2a.redacted_agent_card shape the card served to unauthenticated callers — the reduced view produced by redact_agent_card(), never the full card. It's a placeholder group: new options that change what the redacted card contains are added here going forward, rather than as unrelated top-level a2a.* fields.

Field Default Purpose
enable_skills false Per-agent developer opt-in to include skills on the redacted card served to unauthenticated callers. Has no effect unless a2a.enable_unauthenticated_well_known_route is also true. When false (default), the redacted card's skills list is empty. When true, it matches the authenticated card's.

Client-side configuration reference: okta_cross_app_access

These fields configure how the calling agent authenticates when invoking a remote XAA-protected agent.

Field Default Purpose
principal_id IDP_AGENT_ID env var Okta AI agent principal ID.
private_jwk IDP_AGENT_PRIVATE_KEY_JWK env var Base64-encoded or raw-JSON private JWK.
okta_token_header — Deprecated, no effect. Removed in a future release; delete it.
fallback_token_headers — Deprecated, no effect. Removed in a future release; delete it.

The incoming token headers are not configurable. The access token is read from x-datarobot-external-access-token. That header lives in dragent/inbound_token.py and is shared with inbound audience validation, so the agent cannot exchange a token from a header validation did not inspect.

The former okta_token_header and fallback_token_headers fields are retired. Setting one to the value it used to default to still loads, with a deprecation warning in the startup log. Any other value — including fallback_token_headers: [], which used to disable the fallback — is rejected at startup, because honouring it would read a header audience validation does not inspect. To upgrade, remove the okta_token_header and fallback_token_headers lines from the authentication: entry in the agent's workflow.yaml; nothing replaces them.

Agent card mapping

The cross_application_access configuration is split across two parts of the published A2A agent card:

  • securitySchemes.oauth2.flows.clientCredentials — Standard OpenAPI fields: tokenUrl (from token_request.token_url) and scopes (from token_request.scopes).
  • capabilities.extensions[] (URI: urn:ietf:params:oauth:grant-type:jwt-bearer) — Non-standard XAA parameters for SDK consumption: token_endpoint_auth_method, token_exchange.* (including hardcoded grant_type and requested_token_type URNs), and token_request.audience.

In addition, the agent card may include up to two identity extensions:

  • urn:datarobot:agent:identity:internal — Emitted automatically in deployed environments (when MLOPS_DEPLOYMENT_ID is set). Carries deployment_id for internal DataRobot routing. Not emitted in local development.
  • urn:datarobot:agent:identity:external — Emitted when external.id is set in workflow.yaml. Carries the developer-supplied catalog identifier.

This separation follows the A2A spec convention: standard OAuth fields belong in securitySchemes, while flow-specific parameters go in capabilities.extensions.

Troubleshooting

Common issues

Symptom Cause Fix
Authorization header missing on A2A RPC calls The remote agent card declares securitySchemes but the client uses datarobot_api_key. When securitySchemes are present, the A2ACredentialService performs OAuth2 security-scheme negotiation and drops incompatible credentials. Switch to an OAuth2-compatible auth provider (e.g. okta_cross_app_access) that matches the security scheme advertised by the remote agent card.
401 Authorization audience claim validation failed The token was issued for a different resource. Request a token whose aud matches the serving agent's token_request.audience.
422 Malformed authorization token The value in x-datarobot-external-access-token is not a decodable JWT. Check what the caller forwards; an opaque token or API key in that header will not decode.
RuntimeError: No IdP access token in request context x-datarobot-external-access-token holds no token. If the message also mentions an ignored okta_token_header override, that is the cause. Forward the Okta token in x-datarobot-external-access-token; authorization is never read as an IdP carrier.
ValueError: principal_id is required IDP_AGENT_ID env var not set. Set IDP_AGENT_ID in your environment or Runtime Parameters.
ValueError: Could not parse private_jwk IDP_AGENT_PRIVATE_KEY_JWK is neither valid base64-encoded JSON nor raw JSON. Verify your JWK — try echo $IDP_AGENT_PRIVATE_KEY_JWK | base64 -d | python -m json.tool.
ValueError: Agent card ... missing required fields Remote agent card doesn't have the XAA extension. Verify the remote agent has cross_application_access configured.
RuntimeError: Failed to fetch agent card Network/auth issue reaching the agent card URL. Check the url in the function_groups config and network connectivity.