Skip to content

HomeSecurityPair agents with a code, not a pasted token

Updated 2026-10-23 · applies to a2a-exposed 0.8.11

Pair agents with a code, not a pasted token

a2a-exposed pairs agents with the OAuth 2.0 Device Authorization Grant (RFC 8628): the connecting agent shows a short code, you approve it, and that agent gets its own revocable token. Nobody pastes a token into chat.

The agent card advertises this flow (oauth2SecurityScheme with a deviceCode flow).

The flow

  1. Agent A runs npx -y a2a-exposed@latest connect https://b.example.com. It prints a code such as WDJB-4827 and a link to B's /device page. A's human relays the code to B's owner. The example code is illustrative.
  2. B's agent is woken with kind: "pairing_request" (code, link, claimed name, card URL). It asks its human, and you approve it.
  3. B's owner approves on the dashboard, on /device with the approval password, or with pair approve <code> and the owner token. Deny confirms on the page. The code lasts 60 minutes. Polling agents see the request in inbox and pair list.
  4. A's connect receives a per-peer token, stores it as an outbound peer, and keeps the token off the terminal. B's token list shows via pairing. token revoke ends it.

Set the approval password once, yourself:

npx -y a2a-exposed@latest pair set-password --web

That prints a one-time link. You open it. The agent must not open it. You can also type pair set-password in a terminal. The hash is salted PBKDF2-SHA256.

OpenID Connect is available on /device and the MCP consent page. pair set-oidc writes the issuer, client id and allowlist. The client secret is an environment variable or --secret-stdin. The button appears when the issuer, client id, secret and allowlist are all set. Until then the page is the password form. Deny confirms on the page. This is optional configuration of the same approval step, and the person still approves.

Who approves

The default is human. The owner token (pair approve <code>) approves, and the decision is recorded as the owner. The approval password on /device also approves. A signed-in person approves on the dashboard with the session. On a hosted agent, Approve and Deny use a session from the last 60 minutes. After 60 minutes, the page asks you to sign in again and returns you to that request. Revoke, rotating the owner token, and deleting the agent ask for a sign-in from the last 10 minutes. On an agent in your Cloudflare account, Approve, Deny, revoke, rotating the owner token, and deleting the agent ask for a sign-in from the last 10 minutes. A visit keeps the sign-in time when that sign-in is already past the 10-minute window, including a setup that is still open for another agent. An open setup moves the window forward while the sign-in is already inside those 10 minutes, so the next step of that setup stays signed in. The owner token is the credential for that call. --pairing-approval agent records the approval as the agent, after the person says yes in chat. off turns the pairing endpoints off, and token issue adds a peer.

What is standard

Piece Where
Device authorization and token endpoints POST /oauth/device_authorization, POST /oauth/token (RFC 8628 (opens in a new tab))
Authorization-server metadata /.well-known/oauth-authorization-server (RFC 8414 (opens in a new tab))
Resource metadata on 401 WWW-Authenticate with resource_metadata (RFC 9728 (opens in a new tab))
Card A2A 1.0 oauth2SecurityScheme with deviceCode

Any OAuth device-flow client can pair, including clients other than the a2a-exposed CLI.

Abuse protection

Device codes are 256 random bits, stored hashed, single-use, and valid for 60 minutes. POST /oauth/device_authorization returns expires_in of 3600. Wrong passwords lock out per code, per IP and globally. New requests are capped per IP and in total. /device and /device/setup are HTML forms, with a strict content security policy, uncached responses, and CSRF protection. An unknown or expired code on GET /device is HTTP 404, with the same "Not found" wording as the owner API. See troubleshooting.

If /device hits Cloudflare error 1102 on the free plan while hashing, lower the cost with deploy --pbkdf2-iterations 50000 (minimum 50,000) and set the password again.

Re-pairing and revocation

connect --replace swaps the token under the same label only if the presented token is still the one stored. A revoke or rotate in between does not let the old token take the label back. token revoke <label> ends access. Tasks keep the label of the peer that sent them.

A PEER_<ALIAS>_TOKEN variable in the environment overrides the saved token. If the two differ, connect, send and status warn and tell you to unset it. They keep both values off the screen.

One token per peer

Each peer token is shown once, stored as a hash, and revoked by label. The rest of the model is on security. Card fields are on agent card.