Authdog

A2A

Last updated Oct 22, 2026
View as Markdown

An A2A agent names Authdog on its public agent card, then admits message calls only after require_auth accepts the bearer. The client exchanges client_credentials out of band and sends the token on the HTTP request. The JSON-RPC body stays the message.

Sample: a2a/python in authdog/agents-samples. Python 3.10+. Package a2a-sdk[http-server]==1.2.3.

Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under A2A in AI.

Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under A2A in AI.

Channel auth, machine credentials, tool checks, and instructions for this framework are the other pages under A2A in AI.

Two surfaces

Surface Auth
GET /.well-known/agent-card.json Public. Names the token URL and scope agent:invoke.
POST /a2a/ authdog.starlette require_auth before the executor runs.

The card is how a client discovers the scheme. The middleware is the gate. Publishing the card does not protect SendMessage.

Declare the scheme

Build an OAuth 2 client-credentials scheme whose token URL is the environment token_endpoint. Attach it as security scheme authdog with scope agent:invoke.

flow = ClientCredentialsOAuthFlow(token_url=token_url)
flow.scopes["agent:invoke"] = "Call this agent"
scheme = SecurityScheme(
    oauth2_security_scheme=OAuth2SecurityScheme(
        description="Authdog agent identity (client credentials)",
        flows=OAuthFlows(client_credentials=flow),
    )
)
requirement = SecurityRequirement()
requirement.schemes["authdog"].list.append("agent:invoke")

Pass security_schemes={"authdog": scheme} and security_requirements=[requirement] on the AgentCard. The token URL is public configuration. The client secret never goes on the card.

Gate the JSON-RPC mount

Construct Authdog(public_key=...) from PK_AUTHDOG. Wrap the /a2a routes in middleware that calls require_auth and copies the subject onto the request before the A2A handler runs.

class _Gate(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        try:
            user = await authdog.require_auth(request)
        except HTTPException:
            return JSONResponse(
                {"error": "Unauthorized"},
                status_code=401,
                headers={"WWW-Authenticate": "Bearer"},
            )
        request.state.authdog_subject = subject_of(user)
        return await call_next(request)

subject_of reads user["id"], then user["sub"]. A custom A2A context builder copies authdog_subject into context.call_context.state. The executor replies with subject:<id> and does not look at the raw Authorization header.

SendMessage requires the A2A-Version: 1.0 header. Without it the SDK reports that 0.3 is not supported, which is a protocol error, separate from the 401.

Call it

response = await http.post(
    token_url,
    data={"grant_type": "client_credentials"},
    auth=(client_id, client_secret),
    headers={"Content-Type": "application/x-www-form-urlencoded"},
)
token = response.json()["access_token"]

async with httpx.AsyncClient(
    headers={"Authorization": f"Bearer {token}"},
) as http:
    card = await A2ACardResolver(http, base_url).get_agent_card()
    client = ClientFactory(
        ClientConfig(httpx_client=http, streaming=False)
    ).create(card)

The same HTTP client fetches the card and sends the message, so the bearer rides on both. Card fetch succeeds without a token. Message calls do not.

Mint again when expires_in is close. The sample client performs one exchange per run.

What you should see

  1. GET /.well-known/agent-card.json with no token returns 200 and includes the token URL and agent:invoke.
  2. POST /a2a/ with A2A-Version: 1.0 and no bearer returns 401 and {"error":"Unauthorized"}, plus WWW-Authenticate: Bearer. The body does not contain subject:.
  3. The same call with a token userinfo accepts returns a message whose text is subject:<id>.

Configure

Variable Process Purpose
PK_AUTHDOG Server Public key. Selects the identity host.
AUTHDOG_TOKEN_URL Server and client Token URL written on the card and used for the exchange.
A2A_PUBLIC_URL Server URL advertised on the card for the JSON-RPC binding.
AUTHDOG_CLIENT_ID / AUTHDOG_CLIENT_SECRET Client Agent credentials. Server does not read them.
A2A_BASE_URL Client Origin the client resolves the card from.
Read To learn how to
Agent frameworks See the same bearer on the other four hooks
Eve Move the check onto a channel auth walk
Machine identity Create the confidential client

Learn more