Skip to content

fix(server): Authorization: Bearer always accepted; legacy headers additive (#720)#721

Merged
bokelley merged 2 commits into
mainfrom
claude/issue-720-multi-header-bearer-auth
May 13, 2026
Merged

fix(server): Authorization: Bearer always accepted; legacy headers additive (#720)#721
bokelley merged 2 commits into
mainfrom
claude/issue-720-multi-header-bearer-auth

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Closes #720.

Summary

BearerTokenAuthMiddleware previously took an EXCLUSIVE header_name kwarg — setting it to a custom value (e.g. x-adcp-auth) silently rejected every request carrying the spec-canonical Authorization: Bearer header. Every spec-compliant client (browsers, off-the-shelf HTTP libraries, the AdCP security_baseline/probe_api_key storyboard) failed against the middleware named for RFC 6750 because the middleware actively ignored the RFC 6750 carrier.

This PR inverts the semantics: Authorization: Bearer is always accepted; legacy custom headers become additive opt-in aliases.

Surface changes

  • BearerTokenAuthMiddleware — new legacy_header_aliases: list[str] | None + legacy_aliases_bearer_prefix_required: bool kwargs. Old header_name / bearer_prefix_required deprecated, folded into the alias list internally; behavior shifts from EXCLUSIVE → ADDITIVE.
  • BearerTokenAuth dataclass — new legacy_header_aliases / mcp_legacy_header_aliases / a2a_legacy_header_aliases fields. New resolved_mcp_legacy_aliases() / resolved_a2a_legacy_aliases() accessors. Per-leg mcp_header_name / a2a_header_name emit DeprecationWarning.
  • _wrap_mcp_with_auth threads the resolved alias list to the middleware instead of the legacy single-header kwarg.

Behavior change (changelog-worthy)

Pre-#720: mcp_header_name=\"x-adcp-auth\" made the middleware ONLY check x-adcp-auth. Spec-compliant clients silently 401'd.

Post-#720: same config makes x-adcp-auth an additive alias; Authorization: Bearer is also accepted. Existing legacy clients keep working; spec-compliant clients start working. The deprecation warning surfaces the migration.

Adopter migration

# Was: rejects spec-compliant clients silently
BearerTokenAuth(
    validate_token=...,
    mcp_header_name=\"x-adcp-auth\",
    mcp_bearer_prefix_required=False,
)

# Is: both wire carriers work simultaneously
BearerTokenAuth(
    validate_token=...,
    mcp_legacy_header_aliases=(\"x-adcp-auth\",),
)

Test plan

  • 7 new middleware-level tests + 6 new dataclass-level tests covering the additive contract, alias resolution order, deprecation warnings, dedup edge cases.
  • 3 existing tests updated for the new ADDITIVE contract (renamed where they pinned the bug).
  • All 106 auth tests pass locally; full sweep 4267 tests pass.
  • ruff + mypy clean on touched files.

🤖 Generated with Claude Code

bokelley and others added 2 commits May 13, 2026 07:42
…ditive (#720)

``BearerTokenAuthMiddleware`` previously took an EXCLUSIVE
``header_name`` kwarg: setting it to a custom value (e.g.
``x-adcp-auth``) silently rejected every request carrying the
spec-canonical ``Authorization: Bearer`` header. Every spec-compliant
client — browsers, off-the-shelf HTTP libraries, the AdCP
``security_baseline/probe_api_key`` storyboard — failed against the
middleware named for RFC 6750 because the middleware actively ignored
the RFC 6750 carrier.

This PR inverts the semantics:

- ``Authorization: Bearer <token>`` is always accepted. Cannot be
  turned off — it's the spec and it's the class's own name.
- Legacy custom headers become additive opt-in aliases. New middleware
  kwargs: ``legacy_header_aliases``, ``legacy_aliases_bearer_prefix_required``.
  Resolution order: Authorization first; if absent, each alias in
  order, first non-empty wins.
- ``header_name`` / ``bearer_prefix_required`` are deprecated with a
  ``DeprecationWarning`` on construction. Folded into the alias list
  internally so existing configs keep working — but the behavior
  CHANGES from EXCLUSIVE to ADDITIVE.
- ``BearerTokenAuth`` dataclass mirrors: new
  ``legacy_header_aliases`` / ``mcp_legacy_header_aliases`` /
  ``a2a_legacy_header_aliases`` fields. ``resolved_mcp_legacy_aliases()``
  / ``resolved_a2a_legacy_aliases()`` accessors. Per-leg
  ``mcp_header_name`` / ``a2a_header_name`` emit ``DeprecationWarning``.
- ``_wrap_mcp_with_auth`` threads the resolved alias list instead of
  the legacy single-header kwarg.

Adopter migration:

  # Was: rejects spec-compliant clients silently
  BearerTokenAuth(validate_token=..., mcp_header_name="x-adcp-auth",
                  mcp_bearer_prefix_required=False)

  # Is: both wire carriers work simultaneously
  BearerTokenAuth(validate_token=...,
                  mcp_legacy_header_aliases=("x-adcp-auth",))

Tests: 7 new middleware-level + 6 new dataclass-level. 3 existing
tests pinned the old EXCLUSIVE behavior and are renamed/re-targeted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Reject bare-string alias values (``mcp_legacy_header_aliases="x-adcp-auth"``
  without the trailing comma). Pre-fix, the resolver would walk it
  letter-by-letter. Loud ``ValueError`` at construction with a hint
  pointing at the fix — same loud-fail posture as the existing
  empty-string check on ``header_name``.

- Reject ``"authorization"`` in any of the new-shape alias fields at
  construction. The spec-canonical header is always accepted by the
  middleware; listing it as an alias is a no-op and almost always a
  config error.

- Type ``*_legacy_header_aliases`` fields as ``Sequence[str] | None``
  so list literals work alongside tuples. Pairs with the
  trailing-comma foot-gun guard — agents generating list-shaped
  config no longer hit a frozen-dataclass type error.

- Update the dataclass docstring's worked example to show the new
  ``mcp_legacy_header_aliases=[...]`` shape instead of the deprecated
  ``mcp_header_name=...``.

- Comment in ``_wrap_mcp_with_auth`` explicitly says the dataclass-
  level deprecation warning is the only one that fires (the
  middleware-level warning is suppressed because the dataclass routes
  through the alias path).

- Comment in ``_merge_alias_sources`` notes the preserved alias casing
  is display-only — the middleware lowercases at construction before
  wire matching.

- 5 new tests pin the contracts: bare-string foot-gun rejection,
  ``authorization`` rejection, empty-alias rejection, list-form
  acceptance, no-spurious-warning on the new-shape happy path.

- 1 existing test renamed and re-targeted: it pinned the
  silent-drop behavior of ``authorization`` listed as an alias;
  the new validation makes that a construction error, so the test
  now exercises the back-compat ``header_name="authorization"``
  shim path's filter instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit dd6503e into main May 13, 2026
16 checks passed
@bokelley bokelley deleted the claude/issue-720-multi-header-bearer-auth branch May 13, 2026 11:52
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.

BearerTokenAuthMiddleware: Authorization: Bearer should be the default; legacy header should be opt-in alias

1 participant