Skip to content

feat(auth): per-leg header config + agent-card bearerAuth scheme#595

Merged
bokelley merged 1 commit intomainfrom
bokelley/issue-57
May 7, 2026
Merged

feat(auth): per-leg header config + agent-card bearerAuth scheme#595
bokelley merged 1 commit intomainfrom
bokelley/issue-57

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 7, 2026

Summary

Closes upstream items 1 and 2 from salesagent#57. With these in place, the BearerToAdcpAuthMiddleware shim in salesagent PR #60 becomes deletable.

  • Per-leg header knobs on BearerTokenAuthmcp_header_name / a2a_header_name / mcp_bearer_prefix_required / a2a_bearer_prefix_required. One config drives both legs without a translation middleware. Setting both legacy header_name and a per-leg knob raises ValueError at construction.
  • Agent card auto-publishes a security envelope — when BearerTokenAuth is configured, _build_agent_card emits a matching securitySchemes + securityRequirements so a2a-sdk's client auth interceptor attaches credentials instead of short-circuiting against an empty envelope. Bearer-prefix configs publish HTTPAuthSecurityScheme(scheme="bearer") under id bearerAuth; raw-token custom-header configs publish APIKeySecurityScheme under id adcpAuth.

Defaults & back-compat

Authorization: Bearer <token> (RFC 6750) is the protocol-canonical carrier on both legs. It's the only header backed by an actual RFC, what every off-the-shelf MCP / A2A / HTTP client emits by default, and what the AdCP spec is moving toward as canonical for both transports.

x-adcp-auth is an opt-in legacy-compat alias, not a default — for adopters with deployed MCP clients that send a raw token in a custom header and can't be updated. Reach for it via:

BearerTokenAuth(
    validate_token=...,
    mcp_header_name="x-adcp-auth",
    mcp_bearer_prefix_required=False,
)

Defaults preserve pre-#57 single-knob behavior — existing BearerTokenAuth(validate_token=...) adopters keep working unchanged.

Construction guards (__post_init__)

  • Mutually exclusive: legacy single-knob vs per-leg knobs for the same axis.
  • Empty / whitespace-only header names rejected (would silently 401 every request).
  • Authorization + bearer_prefix_required=False rejected — RFC 7235 reserves Authorization for <scheme> <credentials>. Raw-token schemes need a custom header name.

Out of scope

  • Item 3 from salesagent#57 (spec text declaring Authorization: Bearer canonical with x-adcp-auth as a documented compat alias) is a separate spec-repo PR.

Test plan

  • ruff check src/ clean
  • mypy src/adcp/ clean (783 source files)
  • pytest tests/ -v — 4165 passed (+8 new tests vs prior run)
  • New file tests/test_auth_per_leg_headers.py — 22 tests covering: resolved-default precedence, legacy single-knob back-compat, per-leg overrides, conflict guards, empty-header rejection, RFC 7235 misuse rejection, A2A middleware honors per-leg config, A2A header-name case-insensitive match, agent-card scheme variants (bearerAuth HTTP / adcpAuth API-key), end-to-end MCP per-leg routing through BearerTokenAuthMiddleware, end-to-end agent-card route serialization
  • Reviewed by ad-tech-protocol-expert, security-reviewer, code-reviewer, adtech-product-expert; punch-list addressed (RFC 7235 misuse guard, scheme-variant id branching, dropped non-standard bearer_format, empty-header rejection, missing E2E + case-sensitivity tests)

🤖 Generated with Claude Code

Add per-leg header knobs to BearerTokenAuth and auto-publish a matching
security scheme on the A2A agent card so a2a-sdk's client interceptor
attaches credentials without seller-side intervention.

Defaults preserve pre-#57 single-knob behavior (Authorization + Bearer
prefix on both legs — RFC 6750, the protocol-canonical carrier). The
mcp_/a2a_ knobs are an opt-in escape hatch for adopters with legacy
clients that send a raw token in a custom header (e.g. x-adcp-auth);
the spec is moving toward Authorization: Bearer canonical for both
transports.

Closes salesagent#57 items 1 and 2; item 3 is a separate spec-repo PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 52f45ef into main May 7, 2026
16 checks passed
@bokelley bokelley deleted the bokelley/issue-57 branch May 7, 2026 21:56
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.

1 participant