A2A auth is not first-class in kagent itself — the built-in HTTP server only forwards headers, it does not validate them. Authentication is delegated to the layer in front of the agent's A2A port. Here are the three shapes that actually work.
For internal platform agents, use Istio mTLS + AuthorizationPolicy.
For agents called by users or external systems, put agentgateway in front of the
A2A service with a jwtAuth policy. kagent's own A2A handler does
not verify tokens — your verifier must be upstream.
go/core/internal/httpserver/auth/authn.go currently ships an
UnsecureAuthenticator. It reads X-User-Id and the
Authorization header and forwards them to the downstream agent. It does
not validate the JWT, the user ID, or anything else.
That means out-of-the-box A2A traffic into a kagent agent is unauthenticated. Anything
that can reach the agent's Service IP can call message/send and
message/stream. You bring your own identity verifier — and the agent
runtime is fine with that, because the headers reach the downstream code as advisory
identity.
The upstream agentgateway repo at examples/a2a/ proxies A2A
with a single policy that marks the route as A2A and handles agent-card URL rewriting:
policies:
a2a: {}
backends:
- host: localhost:9999
That policy alone does not authenticate. It stacks with the generic route policies — which is where the auth lives:
| Policy | What it does | Fit for A2A |
|---|---|---|
jwtAuth |
Validate JWT on each call — issuer, audience, JWKS URL, claim requirements. | Primary |
mcpAuthentication |
Built for MCP, but is JWT validation underneath (issuer / JWKS / audience / mode: strict|optional|permissive). Works on A2A routes too. | Compatible |
oidc |
Full OIDC authorization-code flow with session cookies. Overkill for service-to-service; useful when a browser-UI agent is on the path. | UI only |
extAuthz |
Delegate to an external authorizer (OPA, custom service). Slower; use when authz logic is too rich for CEL. | Optional |
cors |
Required if a browser-side A2A client calls the gateway directly. | Browser only |
JWT validation supports Auth0, Keycloak, generic OIDC, or a static JWKS file/URL.
See schema/config.md:160-180 and examples/mcp-authentication/
in the upstream agentgateway tree for the full surface.
The caller agent attaches a bearer token. agentgateway validates it against the IdP's JWKS, then forwards the request — usually re-attaching identity claims as headers that the downstream agent can read. kagent's internal handler accepts those headers as advisory; the enforcement already happened upstream.
Front each agent's A2A Service with agentgateway. Stack a2a: {} +
jwtAuth. Caller sends Authorization: Bearer <JWT>;
gateway validates and propagates. Agent-card URL rewrite handled for free —
callers can't bypass.
Use the mesh you already have. RequestAuthentication validates the JWT;
AuthorizationPolicy scopes which workload identities (SPIFFE IDs) can
call which agent. No agent-side code change. You already have an example at
platform/agentgateway/istio-authorization-policy.yaml.
For fully in-cluster A2A graphs where workload identity is enough.
PeerAuthentication: STRICT + AuthorizationPolicy keyed on
source.principal. No JWT plumbing, no token rotation, no IdP dependency
for the data path.
| Question | Pattern 1 (gateway) | Pattern 2 (Istio + JWT) | Pattern 3 (mTLS only) |
|---|---|---|---|
| End-user identity propagated to the agent? | yes | yes | no |
| Workload-only identity sufficient? | overkill | overkill | yes |
| Requires Istio? | no | yes | yes |
| Per-agent routing / agent-card rewrite? | yes | no | no |
| External callers across cluster boundary? | yes | with east-west gateway | no |
| Operational cost | medium | medium | low |
securitySchemes in the agent card, but neither
kagent nor agentgateway turns those into a runtime enforcement policy automatically.
You wire the JWT validation manually on the route.
UnsecureAuthenticator is the only built-in option. There is no
in-process JWT verifier yet — the assumption is that the gateway or mesh handled it.
docs/agentgateway-mcp-tool-auth/TOOL-AUTH-DISCOVERY.md
for A2A — describing how to project ToolGrant-equivalent
agent-to-agent grants into gateway policy — is the documented gap. This page is the
first draft of that companion.
A minimal route policy that pairs a2a: {} with JWT validation. Caller
attaches a bearer token; gateway verifies issuer, audience, and JWKS before letting
the request reach the kagent agent's A2A port.
binds:
- port: 3000
listeners:
- routes:
- policies:
a2a: {}
jwtAuth:
issuer: https://idp.example.com/realms/platform
jwksUrl: https://idp.example.com/realms/platform/protocol/openid-connect/certs
audiences:
- platform-knowledge-agent
mode: strict
backends:
- host: platform-knowledge-agent.kagent.svc.cluster.local:8080
mode: strict means missing or invalid tokens get 401.
Use optional only while migrating callers from anonymous to
authenticated; never in production for sensitive agents.
For platform-internal agents that only talk to other in-cluster agents, this is the cheapest credible answer. No JWTs to rotate, no IdP on the data path, identity is the workload's SPIFFE ID. Pair with NetworkPolicy to deny everything not on the mesh.
For agents reached by users, by external systems, or across trust boundaries, put agentgateway in front of the A2A Service and verify a JWT from your IdP. Agent-card rewrite, A2A-aware logging, and per-route policy all land in the same layer.
A useful next deliverable: a concrete a2a-auth-discovery-demo.yaml sibling
to the existing mcp-tool-auth-discovery-demo.yaml, showing the catalog →
grant → gateway policy projection specifically for agent-to-agent calls. That closes
the documented gap.