Skip to content

fix(keycardai-oauth): warn once when a TokenVerifier is built without an audience (SDK-4) - #319

Merged
Larry-Osakwe merged 1 commit into
mainfrom
devin/1789606652-sdk4-audience-warn
Sep 17, 2026
Merged

Larry-Osakwe merged 1 commit into
mainfrom
devin/1789606652-sdk4-audience-warn

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

Implements the spec-version 2 construction rule from keycard-sdk-spec specs/server-bearer-auth/bearer-token-verification-middleware.md (SDK-4): building the bearer middleware or the verifier it wraps without an audience emits exactly one warning at construction, naming the option to set, never per request. Behavior when an audience is configured does not change.

Fix-level change: additive warning, no signature change.

class MissingAudienceWarning(UserWarning): ...   # server/exceptions.py, next to VerifierConfigError

class TokenVerifier:
    def __init__(self, issuer, ..., audience=None, ...):
        ...
        if audience is None:
            warnings.warn("This TokenVerifier has no audience configured, so it accepts a token "
                          "minted for any resource in the zone; pass audience= with this "
                          "server's registered resource identifier.",
                          MissingAudienceWarning, stacklevel=2)

The single site covers keycardai-starlette (provider.py get_token_verifier) and keycardai-mcp (server/auth/provider.py get_token_verifier), which pass their audience through; no second warning there. keycardai-langchain always passes audience=resource and keycardai-fastmcp derives its own audience, so neither changes.

Export: MissingAudienceWarning is exported from keycardai.oauth.server (where TokenVerifier itself is exported). The brief asked for keycardai.oauth "alongside VerifierConfigError", but VerifierConfigError is not exported at the top level, and importing .server.exceptions from keycardai/oauth/__init__.py triggers the existing credentials.py -> keycardai.oauth circular import (ImportError: cannot import name 'ClientConfig' from partially initialized module), so the top-level export is not added.

Docs:

  • packages/mcp/README.md: audience="http://localhost:8000/mcp" on all twelve AuthProvider( snippets, plus one sentence after the Quick Start explaining it.
  • packages/starlette/README.md: the "leaving audience unset disables the check" paragraph now says it also warns once at construction.
  • packages/starlette/src/keycardai/starlette/routers/metadata.py: the protected_router docstring example passes an audience.
  • packages/fastmcp/README.md untouched (audience derived from mcp_base_url).

Tests (packages/oauth/tests/keycardai/oauth/server/test_verifier.py): pytest.warns(MissingAudienceWarning) exactly once without an audience; warnings.catch_warnings(record=True) empty with a str audience and with a dict audience; the export check. No package's pytest config turns warnings into errors (packages/mcp only ignores websockets/uvicorn deprecations), so existing fixtures without an audience keep passing and only gain a warning in the summary.

Verification

  • just check: All checks passed.
  • just typecheck: All checks passed (all seven packages).
  • Package tests (uv run --frozen pytest tests from each package directory): oauth 535 passed, starlette 121 passed, langchain 89 passed, mcp 582 passed / 18 skipped / 2 failed. The two mcp failures (test_auth_provider_init_with_zone_id, test_no_application_credential_does_enable_registration) reproduce with this change stashed; they assert NoneAuth when no credential is passed, but the shell's KEYCARD_CLIENT_ID/SECRET are discovered into a BasicAuth. Same pre-existing pair noted on fix(keycardai-starlette): stop rewriting the proxied authorization_endpoint (SDK-3) #316.
  • No em dash in added lines; no Co-Authored-By trailer written by me (the repo's commit hook appends one).

Link to Devin session: https://app.devin.ai/sessions/bbc062cf32b548ef9c4b8b444d2ec67f
Open in Devin Desktop: https://app.devin.ai/desktop/session/bbc062cf32b548ef9c4b8b444d2ec67f?variant=devin
Requested by: @Larry-Osakwe

… an audience (SDK-4)

TokenVerifier(audience=None) now emits one MissingAudienceWarning at
construction naming the option to set. Behavior with an audience is
unchanged; no signature change.

Co-Authored-By: Larry Osakwe <larry@keycard.ai>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@Larry-Osakwe Larry-Osakwe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Single warning site in TokenVerifier, covered by pytest.warns and two silent cases, providers pass through untouched, and the mcp README snippets all bind an audience. Export from keycardai.oauth.server instead of the top level is right: VerifierConfigError is not top-level either.

@Larry-Osakwe
Larry-Osakwe merged commit 4af41fb into main Sep 17, 2026
14 checks passed
@Larry-Osakwe
Larry-Osakwe deleted the devin/1789606652-sdk4-audience-warn branch September 17, 2026 01:26
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