Skip to content

feat: sign in to OAuth agents from the CLI and add a2a auth commands - #130

Closed
mykytanetipa wants to merge 1 commit into
mykytanetipa/oauth-signinfrom
mykytanetipa/oauth-cli
Closed

mykytanetipa wants to merge 1 commit into
mykytanetipa/oauth-signinfrom
mykytanetipa/oauth-cli

Conversation

@mykytanetipa

Copy link
Copy Markdown

Calling an OAuth-protected agent previously meant fetching a token elsewhere and passing it by hand until it expired. This wires OAuth into agent commands so they sign in on demand and retry once. It also adds login, logout, list, and get support under the auth command group.

Example:

# ~/.config/a2a-cli/config.yaml
oauth-clients:
  - authority: https://auth.example.com
    client_id: my-cli-client
$ a2a send -a https://agent.example.com "hello"
To authorize, open:
https://auth.example.com/authorize?client_id=my-cli-client&...
Authorized. Grant saved for https://agent.example.com.
[agent] Hi! How can I help?

$ a2a auth login https://agent.example.com --device
To authorize, open: https://auth.example.com/activate
Enter code: EB45-E541
Authorized. Grant saved for https://agent.example.com.

$ a2a auth list
RESOURCE                   ISSUER                    CLIENT         SCOPES      EXPIRES
https://agent.example.com  https://auth.example.com  my-cli-client  a2a.invoke  2026-11-03T22:57:51Z (refreshable)

$ a2a auth logout https://agent.example.com
Logged out of https://agent.example.com (revoked)

When an agent command hits an interaction-required error on an interactive terminal, the CLI runs the sign-in prompt on stderr under its own five-minute timeout. Once authorized, it saves the grant and reruns the command once with a fresh request timeout, skipping the retry if a stream already printed events. In pipes, scripts, CI, or explicit cached mode, the CLI never waits on a browser and exits immediately with a hint showing the login command to run. The bundled agent skill instructions use cached mode the same way so coding agents ask the user to log in rather than hanging.

Explicit credentials stay in charge: when the auth flag, a service parameter, or a credential helper already supplies the Authorization header, OAuth stays out of the way. Pre-registered clients are configured per authorization server on the command line or in the user config file, and ignored in any discovered local .env file; a separate setting changes or turns off the default client metadata document. Logging out of one agent revokes its grants upstream when the server supports revocation, deletes them locally, and leaves other agents on the same host signed in.

Refs #98

Calling an OAuth-protected agent meant fetching a token elsewhere and
passing it with --auth until it expired. Agent commands now sign in when
the agent requires it and retry once, with new --timeout contexts after
the sign-in, which has its own 5 minute limit. A stream that already
printed events is not restarted.

--oauth selects auto (default: sign in when a terminal is attached),
cached (stored grants only, for scripts and sandboxed agents) or off.
--oauth-clients takes client registrations as JSON or, in config files,
a list of {authority, client_id, scopes} objects, and is not read from a
discovered .env. --oauth-default-client-id changes or disables the
Client ID Metadata Document used when no registration matches. Grants
are kept in the file store. No OAuth runs when --auth, --svc-param or a
credential helper supplies the Authorization header. Requests to
authorization servers go through the same transport as agent requests,
so --unix-socket and --ca-cert apply to them.

a2a auth login gets a grant without sending a message (--device,
--scope), auth logout revokes and deletes the grants that apply to the
agent, auth list lists them, and auth get shows stored grants next to
credential helper headers. Errors use A2ACLI_ERR_CREDENTIALS_MISSING and
A2ACLI_ERR_AUTH_FAILED, both with exit code 4, as in SPEC Appendix D.

internal/README.md documents where the CLI learns how to sign in, the
sign-in flows, grant storage and refresh, the --oauth flags, client
registrations and the auth commands. The skill tells agents to run with
--oauth cached and to ask the user to run the a2a auth login command
from the error's hint.

Refs #98
Comment thread internal/cli/auth_list.go
}
var out []oauth.Grant
for _, gr := range grants {
if oauth.ResourceCovers(gr.Key.Resource, target) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

card grants are stored under the interface url, so auth logout https://agent.example.com finds nothing when the interface is at /a2a/v1, and it revokes a parent grant shared by sibling agents. let's resolve the card to interface urls like auth login does. let's please also add a test with an agent served under a path

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in mykytanetipa#6. auth get and auth logout resolve the card and select the grant for the URL plus the card's interface URLs. A parent grant shared with sibling agents is kept, and the output shows the command that removes it. TestAuthAgentUnderPath covers an agent at /a2a/v1 next to a sibling.


// challengeRequirement asks an interface without credentials for its Bearer
// challenge and follows it, or the well-known locations, to the authorization server.
func (g *globalConfig) challengeRequirement(ctx context.Context, baseURL string) (*oauth.Requirement, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move this to oauth.Manager, it mostly duplicates transport.go and we keep logic out of the cli package

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved. Manager.RequirementForURL in a2aproject/a2a-go#570 does the probe, and auth login in mykytanetipa#6 calls it after a real A2A call. The CLI copy is gone.

@mykytanetipa

Copy link
Copy Markdown
Author

Closing in favor of the a2a-go OAuth client (a2aproject/a2a-go#568, a2aproject/a2a-go#569, a2aproject/a2a-go#570) and the CLI changes built on it (mykytanetipa#6, mykytanetipa#7).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants