A2A Gateway: JWT/Bearer authentication and end-to-end claims propagation#480
Merged
Conversation
Add JWT/Bearer authentication as a second gateway auth scheme (generic OIDC via configurable Authority/Audience) alongside the existing X-Api-Key scheme, and wire end-to-end claims propagation so the agent can mark caller identity as verified rather than self-asserted. - WellKnownHeaders: add rb-auth-claims for gateway-verified caller claims - Gateway: AddA2AJwtBearerAuthentication + multi-scheme default policy (accepts API key OR Bearer); advertise bearer/openId on the agent card as separate (OR-ed) security requirements - RockBotBridgeHandler: forward verified JWT claims (sub/name/iss/scope) as rb-auth-claims for Bearer callers; API-key callers stay name-based - Agent: ClaimsForwardingAgentIdentityVerifier (new default) builds a non-self-asserted identity from forwarded claims, falling back to the name-based verifier when absent - Host config + design/a2a-gateway-auth.md documenting OIDC options and the propagation contract - Tests for the agent card, claims-header builder, and the verifier - Bump version to 0.13.0 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a real end-to-end test of JWT/Bearer auth and claims propagation, and fix a 401-challenge regression surfaced by it. - docker-compose.a2a-test: add an oidc-server-mock issuer (password grant, JWT access tokens) and configure the gateway with Jwt__Authority; pass OIDC token endpoint + creds to the test harness - JwtAuthScenarios: card advertises bearer/openId; valid bearer token accepted; invalid token rejected (401); claims propagated — the agent records the JWT caller under its sub with IsSelfAsserted=false and the IdP issuer - AgentTrustEntry: record verification provenance (Issuer, IsSelfAsserted) so trust decisions/audits can tell verified callers from self-asserted ones; RockBotTaskHandler populates it from the verified identity - Gateway auth: replace the multi-scheme authorization policy with a forwarding policy scheme (Bearer when Authorization: Bearer present, else API key). A single scheme now authenticates/challenges per request, fixing a double-challenge that reset the connection on 401 (broke both the unauthenticated and invalid-token cases) Validated via docker compose: 18/18 integration scenarios pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #264.
Summary
Adds JWT/Bearer authentication as a second A2A HTTP gateway auth scheme (generic OIDC) alongside the existing
X-Api-Keyscheme, and wires end-to-end claims propagation so the agent can independently treat a caller's identity as verified (IsSelfAsserted: false) rather than trusting a bareSourcestring.JWT/Bearer authentication (generic OIDC)
JwtAuthOptions(config POCO):Authority,Audience,RequireHttpsMetadata. Bearer is enabled only whenAuthorityis set — API-key-only deployments are unaffected.AddA2AJwtBearerAuthenticationregisters the standardMicrosoft.AspNetCore.Authentication.JwtBearerhandler (JWKS auto-discovery) and widens the default authorization policy soPOST /accepts either API key or a valid Bearer token.Agent card advertises both schemes
BuildAgentCardexposesapiKeyalways, plusbearer(HttpAuthSecurityScheme) andopenId(OIDC discovery URL) when JWT is enabled. The two schemes are listed as separateSecurityRequiremententries so they are OR-ed (either satisfies), not AND-ed.End-to-end claims propagation
rb-auth-claimswell-known header.RockBotBridgeHandlerextracts verified claims (sub,name,iss,scope) from Bearer principals and forwards them as a JSON envelope header; API-key callers get no header (stay name-based).ClaimsForwardingAgentIdentityVerifier(now the defaultIAgentIdentityVerifier): builds a non-self-asserted identity from forwarded claims (populatingIssuer+Claims), falling back toNameBasedAgentIdentityVerifierwhen the header is absent.Config + docs
Program.csbinds theJwtsection;appsettings.jsonsample block;design/a2a-gateway-auth.mddocuments the OIDC options and the propagation contract.Design decisions (confirmed during planning)
rb-auth-claims), not the raw token — the trust boundary is the gateway; RabbitMQ is internal. Agent-side token re-validation against JWKS is an explicit non-goal.Testing
AgentCardTests— card advertises apiKey only when JWT is off; apiKey + bearer + openId (as separate OR-ed requirements) when on.RockBotBridgeHandlerTests— Bearer principal emitsrb-auth-claims; API-key principal does not; unauthenticated returns null.ClaimsForwardingAgentIdentityVerifierTests— claims present → verified/non-self-asserted; absent → name-based fallback; malformed/empty handled.Out of scope
🤖 Generated with Claude Code