Skip to content

Commit e077c8b

Browse files
fjumacescoffier
authored andcommitted
Add a post about A2A security
1 parent 5aeb936 commit e077c8b

File tree

1 file changed

+276
-0
lines changed

1 file changed

+276
-0
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
---
2+
layout: post
3+
title: 'How to Secure Your A2A Server Agent with Keycloak OAuth2'
4+
date: 2025-10-28
5+
tags: ai a2a security keycloak oauth2
6+
synopsis: This blog post shows how to secure an A2A server agent using OAuth2 bearer tokens via Keycloak and shows how to enable an A2A client to automatically obtain and pass the required token in each request.
7+
author: fjuma
8+
---
9+
10+
Today, we've released A2A Java SDK 0.3.0.Final which includes security and cloud related enhancements.
11+
In this post, we'll focus on A2A security. Stay tuned for a future post on cloud related enhancements!
12+
13+
The A2A protocol delegates authentication to standard mechanisms like OAuth2 and OpenID Connect. An
14+
A2A server agent specifies its authentication requirements in its agent card so A2A clients know
15+
what type of credentials they need to obtain and then they can pass these credentials to
16+
the server agent when sending requests.
17+
18+
We're going to walk through securing an A2A server agent with OAuth2 using Keycloak and we'll
19+
show how to configure an A2A client to handle token management. Our A2A server agent will be able
20+
to support all 3 transports (JSON‑RPC, HTTP+JSON/REST, and gRPC) so we can see that the authentication
21+
configuration is consistent across transport protocols.
22+
23+
== Magic 8 Ball Sample
24+
25+
To see security configuration in action, we'll use the https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/magic_8_ball_security[Magic 8 Ball Security sample]
26+
from the https://github.com/a2aproject/a2a-samples[a2a-samples] repository. This sample is a simple https://github.com/a2aproject/a2a-samples/blob/main/samples/java/agents/magic_8_ball_security/server/src/main/java/com/samples/a2a/Magic8BallAgent.java[Quarkus LangChain4j AI service] that consults a virtual Magic 8 Ball to answer yes/no questions.
27+
28+
=== A2A Server Agent
29+
30+
We're going to focus specifically on the security configuration for our A2A server agent. For a detailed
31+
description on how to turn a Quarkus LangChain4j AI Service into an A2A server agent, check
32+
out this previous https://quarkus.io/blog/quarkus-a2a-java-grpc/[blog post].
33+
34+
==== Security Configuration
35+
36+
When using the A2A Java SDK reference implementations, the endpoints that need to be secured according to
37+
the A2A specification are already annotated with `@Authenticated` for you. That means that in order to secure
38+
your A2A server agent, you just need to specify the configuration for the specific authentication mechanism
39+
you'd like to use. To secure our A2A server agent with OAuth2, we simply need to add a dependency on the
40+
`quarkus-oidc` extension as shown below:
41+
42+
[source,java]
43+
----
44+
<dependency>
45+
<groupId>io.quarkus</groupId>
46+
<artifactId>quarkus-oidc</artifactId>
47+
</dependency>
48+
----
49+
50+
> **NOTE**: In our sample, we're going to rely on Quarkus Dev Services to automatically create and configure
51+
a Keycloak instance that we'll use as our OAuth2 provider. Quarkus Dev Services relies on a container runtime
52+
like Docker or Podman to be installed and properly configured. For more details on using Podman with
53+
Quarkus, see this https://quarkus.io/guides/podman[guide].
54+
55+
Now that our A2A server agent is being secured with OAuth2, we need to indicate this in our agent card
56+
as shown below.
57+
58+
[source,java]
59+
----
60+
@Produces
61+
@PublicAgentCard
62+
public AgentCard agentCard() {
63+
ClientCredentialsOAuthFlow clientCredentialsOAuthFlow = new ClientCredentialsOAuthFlow( <1>
64+
null, <2>
65+
Map.of("openid", "openid", "profile", "profile"), <3>
66+
"http://localhost:" + keycloakPort + "/realms/quarkus/protocol/openid-connect/token"); <4>
67+
OAuth2SecurityScheme securityScheme = new OAuth2SecurityScheme.Builder() <5>
68+
.flows(new OAuthFlows.Builder().clientCredentials(clientCredentialsOAuthFlow).build())
69+
.build();
70+
71+
return new AgentCard.Builder()
72+
.name("Magic 8 Ball Agent")
73+
.description(
74+
"A mystical fortune-telling agent that answers your yes/no "
75+
+ "questions by asking the all-knowing Magic 8 Ball oracle.")
76+
.preferredTransport(TransportProtocol.JSONRPC.asString())
77+
.url("http://localhost:" + httpPort)
78+
.version("1.0.0")
79+
.documentationUrl("http://example.com/docs")
80+
.capabilities(
81+
new AgentCapabilities.Builder()
82+
.streaming(true)
83+
.pushNotifications(false)
84+
.stateTransitionHistory(false)
85+
.build())
86+
.defaultInputModes(List.of("text"))
87+
.defaultOutputModes(List.of("text"))
88+
.security(List.of(Map.of(OAuth2SecurityScheme.OAUTH2, <6>
89+
List.of("profile"))))
90+
.securitySchemes(Map.of(OAuth2SecurityScheme.OAUTH2, securityScheme)) <7>
91+
.skills(
92+
List.of(
93+
new AgentSkill.Builder()
94+
.id("magic_8_ball")
95+
.name("Magic 8 Ball Fortune Teller")
96+
.description("Uses a Magic 8 Ball to answer"
97+
+ " yes/no questions")
98+
.tags(List.of("fortune", "magic-8-ball", "oracle"))
99+
.examples(
100+
List.of(
101+
"Should I deploy this code on Friday?",
102+
"Will my tests pass?",
103+
"Is this a good idea?"))
104+
.build()))
105+
.protocolVersion("0.3.0")
106+
.additionalInterfaces( <8>
107+
List.of(
108+
new AgentInterface(
109+
TransportProtocol.JSONRPC.asString(),
110+
"http://localhost:" + httpPort),
111+
new AgentInterface(
112+
TransportProtocol.HTTP_JSON.asString(),
113+
"http://localhost:" + httpPort),
114+
new AgentInterface(TransportProtocol.GRPC.asString(),
115+
"localhost:" + httpPort)))
116+
.build();
117+
}
118+
----
119+
<1> Details about the OAuth2 flow our A2A server agent supports.
120+
<2> We can optionally specify the URL to be used for obtaining a refresh token.
121+
<3> The available scopes for the OAuth2 client credentials flow. This is a map between the scope name and a description of the scope.
122+
<4> The token URL to be used for this flow.
123+
<5> Specifies an OAuth2 security scheme using the `ClientCredentialsOAuthFlow`. We'll refer to this from the
124+
agent card.
125+
<6> A list of security requirement objects that apply to all agent interactions. Each object lists
126+
security schemes that can be used. Follows the OpenAPI 3.0 Security Requirement Object.
127+
<7> A declaration of the security schemes that can be used.
128+
<8> Notice that our A2A server agent supports all 3 transports: JSON-RPC, HTTP+JSON/REST, and gRPC.
129+
130+
==== Starting the A2A Server Agent
131+
132+
Follow the https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/magic_8_ball_security#running-the-a2a-server-agent[instructions] in the sample's README to start our A2A server agent.
133+
134+
A2A clients can now send queries to our A2A server agent using any of the 3 configured transports.
135+
136+
Now that our secured A2A server agent is up and running, let's take a look at how to create an A2A
137+
client that can communicate with it.
138+
139+
=== A2A Client
140+
141+
The `magic_8_ball_security` sample also includes a https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/magic_8_ball_security/client/src/main/java/com/samples/a2a/client/TestClient.java[TestClient] that can be used to send messages to the `Magic8BallAgent`.
142+
143+
For general information on how to configure A2A clients using the A2A Java SDK, check out this https://quarkus.io/blog/quarkus-a2a-java-0-3-0-alpha-release/[previous post].
144+
145+
=== Security Configuration
146+
147+
Because the A2A server agent our client will be communicating with is secured using OAuth2, our client
148+
needs to be able to obtain the required token and pass it to the A2A server agent with each request.
149+
150+
The `a2a-java-sdk-client` dependency provided by the A2A Java SDK gives us access to a `Client.builder` that we'll use to create our A2A client and specify the necessary authentication configuration.
151+
152+
The A2A Java SDK provides two main classes related to authentication:
153+
154+
* `CredentialService`: An interface you can implement to define how to obtain a credential for a specific security scheme.
155+
156+
* `AuthInterceptor`: A `ClientCallInterceptor` implementation that uses a `CredentialService` to automatically obtain and attach the required credential to client requests.
157+
158+
Let's see how to configure an A2A client using these classes.
159+
160+
[source,java]
161+
----
162+
// Create credential service for OAuth2 authentication
163+
CredentialService credentialService = new KeycloakOAuth2CredentialService(); <1>
164+
165+
// Create an auth interceptor to be used for all transports
166+
AuthInterceptor authInterceptor = new AuthInterceptor(credentialService); <2>
167+
168+
...
169+
170+
var builder = Client.builder(agentCard)
171+
.addConsumers(consumers)
172+
.streamingErrorHandler(streamingErrorHandler);
173+
174+
// Our client will optionally allow the user to specify which transport to use.
175+
// Here, we'll add configuration for the user-specified transport. The transport
176+
// will default to jsonrpc if not specified by the user.
177+
switch (transport.toLowerCase()) {
178+
case "grpc":
179+
builder.withTransport(
180+
GrpcTransport.class,
181+
new GrpcTransportConfigBuilder()
182+
.channelFactory(channelFactory)
183+
.addInterceptor(authInterceptor) <3>
184+
.build());
185+
break;
186+
case "rest":
187+
builder.withTransport(
188+
RestTransport.class,
189+
new RestTransportConfigBuilder()
190+
.addInterceptor(authInterceptor) <4>
191+
.build());
192+
break;
193+
case "jsonrpc":
194+
builder.withTransport(
195+
JSONRPCTransport.class,
196+
new JSONRPCTransportConfigBuilder()
197+
.addInterceptor(authInterceptor) <5>
198+
.build());
199+
break;
200+
default:
201+
throw new IllegalArgumentException("Unsupported transport type. Supported types are: grpc, rest, jsonrpc");
202+
}
203+
204+
return builder.build();
205+
----
206+
<1> `CredentialService` is an interface provided by the A2A Java SDK. You can implement this interface to
207+
obtain credentials for a given security scheme. In our sample, since the A2A server agent is being secured
208+
with Keycloak, we have created a class called https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/magic_8_ball_security/client/src/main/java/com/samples/a2a/client/KeycloakOAuth2CredentialService.java[KeycloakOAuth2CredentialService] that implements this
209+
interface and obtains credentials for the OAuth2 security scheme using the Keycloak `AuthzClient`.
210+
<2> `AuthInterceptor` is a class provided by the A2A Java SDK that can be used to automatically add credential
211+
details to a request based on the security schemes supported by the A2A server agent using a `CredentialService`.
212+
Notice that we are passing our `KeycloakOAuth2CredentialService` to the `AuthInterceptor`. We're going to use
213+
the same `AuthInterceptor` to specify the authentication configuration for all 3 transport protocols.
214+
<3> Interceptors can be configured for each transport. Here we are specifying that we want to use our `AuthInterceptor` for the gRPC transport.
215+
<4> This shows how to configure the `AuthInterceptor` for the HTTP+JSON/REST transport.
216+
<5> This shows how to configure the `AuthInterceptor` for the JSON-RPC transport.
217+
218+
With this configuration, when our A2A client attempts to sends a request to the A2A server agent,
219+
the `AuthInterceptor` will use the A2A server's agent card to detect its supported security schemes
220+
and will automatically obtain the required credential for the OAuth2 security scheme using the `KeycloakOAuth2CredentialService`. The obtained token will then be included in the HTTP authorization
221+
header for the A2A server agent to validate.
222+
223+
=== Using the A2A Client
224+
225+
The sample application contains a `TestClientRunner` that can be run using JBang:
226+
227+
[source,shell]
228+
----
229+
jbang TestClientRunner.java
230+
----
231+
232+
You should see output similar to this:
233+
234+
[source,shell]
235+
----
236+
Connecting to agent at: http://localhost:11000
237+
Using transport: jsonrpc
238+
...
239+
Sending message: Should I deploy this code on Friday?
240+
Using jsonrpc transport with OAuth2 Bearer token
241+
Message sent successfully. Waiting for response...
242+
Received status-update: submitted
243+
Received status-update: working
244+
Received artifact-update: The Magic 8 Ball says: "Outlook good." It seems like a Friday deployment might be a good idea! What are your thoughts on that?
245+
Received status-update: completed
246+
Final response: The Magic 8 Ball says: "Outlook good." It seems like a Friday deployment might be a good idea! What are your thoughts on that?
247+
----
248+
249+
You can also experiment with sending different messages to the A2A server agent using the --message option as
250+
follows:
251+
252+
[source,shell]
253+
----
254+
jbang TestClientRunner.java --message "Should I refactor this code?"
255+
----
256+
257+
You can try using different transports (`jsonrpc`, `grpc`, or `rest`) with the --transport option:
258+
259+
[source,shell]
260+
----
261+
jbang TestClientRunner.java --transport grpc
262+
----
263+
264+
== Conclusion
265+
266+
This post has shown how to configure security for both A2A server agents and A2A clients.
267+
268+
=== Further Reading
269+
270+
* https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents/magic_8_ball_security[Magic 8 Ball Security Sample]
271+
* https://quarkus.io/blog/quarkus-a2a-java-0-3-0-alpha-release/[Getting Started with Quarkus and A2A Java SDK 0.3.0]
272+
* https://quarkus.io/blog/quarkus-a2a-java-0-3-0-beta-release/[A2A Java SDK: Support for the REST Transport is Now Here]
273+
* https://quarkus.io/blog/quarkus-a2a-java-grpc/[Getting Started with A2A Java SDK and gRPC]
274+
* https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents[A2A Java SDK Samples]
275+
* https://github.com/a2aproject/a2a-java/blob/main/README.md[A2A Java SDK Documentation]
276+
* https://a2a-protocol.org/latest/specification/[A2A Specification]

0 commit comments

Comments
 (0)