Thomas Piacentini
← Workshop

Chapter

Security schemes and in-task authorization

Original text · English

The card declares which credentials an agent accepts, HTTP checks them on every request, and a running task can still stop to ask for an approval.

The planner's last delegation goes to deployer, the only agent in the kit that guards its endpoint. A request without a token gets HTTP 401 before any A2A method runs. A request with the token starts a deploy that stops again, in TASK_STATE_AUTH_REQUIRED, until an operator approves it.

Figure follows that credential from the card to the approval. When you finish this section, you can read a card's security fields, send what they ask for, and scope tasks to their owner.

Fig. 6.1one credential, from the card to an in-task approvalflow
The card names the scheme, HTTP rejects a request without the token before A2A runs, and a running task can still stop for an approval.
The card names the scheme, HTTP rejects a request without the token before A2A runs, and a running task can still stop for an approval. Read top to bottom, in five steps. From step 2 on, the left box is what the planner sends and the right box is what deployer answers. Grey boxes sit outside A2A. The task id is shortened to 8 characters. From capture/out/01-agent-cards.http, 18-extended-card.http, and 07-auth-required.http.

Original SVG

Original source
id: fig-credential-path
kind: flow
title: one credential, from the card to an in-task approval
claim: The card names the scheme, HTTP rejects a request without the token before A2A runs, and a running task can still stop for an approval.
caption: Read top to bottom, in five steps. From step 2 on, the left box is what the planner sends and the right box is what deployer answers. Grey boxes sit outside A2A. The task id is shortened to 8 characters. From capture/out/01-agent-cards.http, 18-extended-card.http, and 07-auth-required.http.

What the card declares

Only the deployer's card carries security fields, so test-runner and code-reviewer accept any caller.

the security fields on the deployer cardjsonFull source
  "securitySchemes": {
    "bearer": {
      "httpAuthSecurityScheme": {
        "description": "A token issued by the platform team.",
        "scheme": "Bearer"
      }
    }
  },
  "securityRequirements": [
    {
      "schemes": {
        "bearer": {
          "list": []
        }
      }
    }
  ],
Cut from the deployer card. The two fields sit between capabilities and defaultInputModes.
Original source
title: the security fields on the deployer card
source: capture/out/01-agent-cards.http
lang: json
note: Cut from the deployer card. The two fields sit between capabilities and defaultInputModes.
---
  "securitySchemes": {
    "bearer": {
      "httpAuthSecurityScheme": {
        "description": "A token issued by the platform team.",
        "scheme": "Bearer"
      }
    }
  },
  "securityRequirements": [
    {
      "schemes": {
        "bearer": {
          "list": []
        }
      }
    }
  ],

Security scheme: one entry in the card's securitySchemes map. The key, here bearer, is a name the agent picks. The value holds exactly one of five variants: apiKeySecurityScheme, httpAuthSecurityScheme, oauth2SecurityScheme, openIdConnectSecurityScheme, or mtlsSecurityScheme {{proto SecurityScheme}}.

Security requirement: one entry in securityRequirements. Its schemes map names schemes from securitySchemes, and each name maps to "the required scopes" {{proto SecurityRequirement}} in a StringList whose only field is list. The deployer names bearer with an empty list, so the token alone is enough.

The §8.5 sample card still says security where the proto says securityRequirements {{spec §8.5}} (conflict D6). The migration guide calls the implicit and password flows removed {{docs whats-new-v1}}, while the proto keeps both as deprecated {{proto OAuthFlows}} (conflict D11), so offer neither.

How credentials travel

The client gets the credential outside A2A and sends it in the headers or metadata of the transport on every A2A request {{spec §7.3}}. That header carries a secret, so a production deployment must encrypt its traffic: HTTPS for the HTTP bindings, and TLS for gRPC {{spec §7.1}}. The specification recommends TLS 1.3 or later {{spec §7.1}}, and the enterprise guide TLS 1.2 or later {{docs enterprise-ready}} (conflict D18).

Without it, deployer answers HTTP 401 with a WWW-Authenticate challenge before any A2A method runs, as extended cards and signatures shows.

Original source
label: every request, checked against the card
source: spec §7.4
---
The server must authenticate every incoming request with the credentials that it receives and the authentication requirements that it declares.

Authorization scope: a task you cannot see does not exist

Each implementation decides its own authorization logic {{spec §7.5}}. The specification fixes how a refusal looks: a server must not reveal that a resource exists when the client has no access to it {{spec §3.3.2}}. So another caller's task gets the same TaskNotFoundError as a task that never existed. The same section lists one case for an authorization error: access to a task that another user created {{spec §3.3.2}}. That error would confirm that the task exists (conflict D36). Answer with the not-found error.

The check runs first: authorization checks must happen before any database query or operation that could leak information {{spec §13.1}}. ListTasks must scope its results to the caller even when the request has no filter {{spec §13.1}}. The SDKs differ on who the owner is:

Implementation Owner scope Another caller's task
Kit none, and the deployer needs one shared bearer token returned
a2a-python 1.2.2 task stores keyed by the call context's user_name, and every unauthenticated caller has the empty name {{sdk src/a2a/server/owner_resolver.py}} {{sdk src/a2a/auth/user.py}} TaskNotFoundError
a2a-js 1.3.0 tenant and owner in every store, with unknown for an anonymous caller {{sdk-js src/server/owner_resolver.ts at v1.3.0}} TaskNotFoundError
a2a-java 1.4.0.Final a TaskAuthorizationProvider interface with no implementation in the release, and without one every task operation fails closed {{sdk-java server-common/src/main/java/org/a2aproject/sdk/server/auth/TaskAuthorizationProvider.java at v1.4.0.Final}} TaskNotFoundError
a2a-go 2.6.0 the in-memory store records the call's user name, and an empty name makes ListTasks fail with ErrUnauthenticated {{sdk-go a2asrv/taskstore/inmemory.go at v2.6.0}} ErrTaskNotFound once a name is set
a2a-dotnet 1.0.0-preview2 none, because the store and the handler carry no principal {{sdk-dotnet src/A2A/Server/ITaskStore.cs at v1.0.0-preview2}} returned
a2a-rs a2a-server-lf-v0.5.1 none in the stores, and an opt-in RequestAuthorizer sees only the headers and the task id {{sdk-rust a2a-server/src/handler.rs at a2a-server-lf-v0.5.1}} returned

Without an authentication middleware that fills in the user, every task is visible to every caller.

In-task authorization

In-task authorization: an agent's request for permission partway through a task, through TASK_STATE_AUTH_REQUIRED {{spec §7.6}}, which interrupted states follows frame by frame. The kit's POST /approve/<task id> checks no credentials, so anyone who learns the task id can approve. A real approval channel authenticates the operator and records who approved.

A client that is itself an agent can pass the request up a chain of tasks in TASK_STATE_AUTH_REQUIRED {{spec §7.6.2}}. An in-band credential then passes through every agent in the chain {{spec §7.6.3}}. So bind it to the agent that asked, and only that agent can use it {{spec §7.6.3}}.

PR #2081 added §7.6.4 to main on 2026-07-30, after v1.0.1, and no release carries it yet. It says that an agent must not treat the move to TASK_STATE_AUTH_REQUIRED on its own as permission for any operation {{spec-main §7.6.4}}. A credential obtained there does not authorize later messages unless the implementation, the issuer, or an extension says so {{spec-main §7.6.4}}. The kit keys each approval by task id, so one approval resumes one deploy, and its bearer check runs on every later request.

Original source
- Send the credential that `securityRequirements` names in an HTTP header on every request.
- Answer `TaskNotFoundError` for every task the caller is not allowed to see.
- Route each approval in `TASK_STATE_AUTH_REQUIRED` through a channel that authenticates the operator.
- Treat an approval as covering one task, and check authorization again on every later message.

Sources: spec §3.3.2, §7.1, §7.3, §7.4, §7.5, §7.6, §7.6.2, §7.6.3, §8.5, §13.1 (research/sources/specification.md); spec-main §7.6.4 (research/sources/specification-main.md, main 679ab3a, unreleased); proto SecurityScheme, SecurityRequirement, StringList, OAuthFlows (research/sources/a2a.proto); docs/topics/enterprise-ready.md and docs/whats-new-v1.md at v1.0.1; sdk src/a2a/server/owner_resolver.py, src/a2a/auth/user.py, src/a2a/server/tasks/inmemory_task_store.py at a2a-python 1.2.2; sdk-js src/server/owner_resolver.ts at v1.3.0; sdk-java server-common/src/main/java/org/a2aproject/sdk/server/auth/TaskAuthorizationProvider.java at v1.4.0.Final; sdk-go a2asrv/taskstore/inmemory.go at v2.6.0; sdk-dotnet src/A2A/Server/ITaskStore.cs at v1.0.0-preview2; sdk-rust a2a-server/src/handler.rs at a2a-server-lf-v0.5.1; a2aproject/A2A PR #2081 (unreleased, on main); capture/out/01-agent-cards.http, 07-auth-required.http, 18-extended-card.http; capture/planner.py, capture/a2a_ref.py, capture/README.md

Code and artifacts 1
What you take away

Saved in your notebook on this device.

Sources and limits

Reading checkpoints are self-reported. Test results are recorded separately and apply to the executed cases.

AI Engineering from Scratch · 3f9715c · MIT

Download this file ↓