Skip to content

AuthKit: auto-bind token audience to resource URL (RFC 8707)#3905

Merged
jlowin merged 2 commits intomainfrom
docs/authkit-rfc-8707
Apr 13, 2026
Merged

AuthKit: auto-bind token audience to resource URL (RFC 8707)#3905
jlowin merged 2 commits intomainfrom
docs/authkit-rfc-8707

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Apr 13, 2026

WorkOS AuthKit now supports RFC 8707 Resource Indicators — developers configure allowed MCP resource URLs in the Dashboard and AuthKit mints tokens with a matching aud claim. That replaces the old client_id-as-audience approximation, which was a weak stand-in that never actually bound tokens to a specific server. The scary warning about token replay risk in the AuthKit docs is no longer true; this PR removes it and makes FastMCP's side of the integration match.

The ergonomic shape matters. Users already pass base_url, and FastMCP already computes the server's canonical resource URL from it (base_url + mcp_path). That same URL is what AuthKit needs in the Dashboard, what gets advertised in .well-known/oauth-protected-resource, and what should be validated as aud. So there's no new parameter — AuthKitProvider auto-binds JWTVerifier.audience in set_mcp_path using the URL it already knows. On startup the server logs the exact string to paste into the Dashboard's "MCP resource indicators" list. One manual action; compliance follows.

# This is the whole integration. No client_id, no audience kwarg.
auth = AuthKitProvider(
    authkit_domain="https://your-app.authkit.app",
    base_url="https://your-server.com",
)
mcp = FastMCP("My App", auth=auth)
# Server logs: "AuthKit tokens will be validated against aud=https://your-server.com/mcp.
#               Configure this URL as a Resource Indicator in the WorkOS Dashboard."

Two supporting changes were needed to make this clean. RemoteAuthProvider.get_routes didn't previously invoke set_mcp_path, which meant subclass lifecycle hooks never fired outside the full OAuth-authorization-server code path — fixed by moving that call into the base. And the JWT verifier's rejection-reason logs (issuer/audience/scopes mismatch) were at DEBUG level, so the generic "Bearer token rejected" was the only signal a user normally saw; those are now WARNING, which would have immediately surfaced the audience mismatch we hit while testing this against a real AuthKit domain.

Also folded in some housekeeping while touching the auth examples: renamed examples/auth/authkit_dcr to examples/auth/authkit (DCR vs CIMD is a Dashboard choice, not an example-level distinction), rewrote the README around the Dashboard setup, and normalized every auth example to use 127.0.0.1 consistently across server/client/README so future RFC 8707 providers don't hit the localhost/127.0.0.1 audience mismatch that bit us during testing.

Breaking: client_id is removed from AuthKitProvider. Callers relying on it should either (a) configure Resource Indicators in the Dashboard and let the provider auto-bind the audience, or (b) pass an explicit token_verifier with their chosen audience — the provider respects user-supplied verifiers and doesn't touch them.

WorkOS AuthKit now supports Resource Indicators (RFC 8707): configure
your MCP server's resource URL in the WorkOS Dashboard and AuthKit mints
tokens with a matching aud claim. This replaces the previous client_id
approximation that could never actually bind tokens to a specific
server.

AuthKitProvider now auto-wires JWTVerifier.audience in set_mcp_path to
equal the resource URL it advertises in protected resource metadata.
Users pass the same (existing) base_url they always did, copy the URL
the server logs on startup into the Dashboard's 'MCP resource
indicators' list, and validation happens automatically. No new public
parameters; client_id is removed.

Other changes:

- Move the RemoteAuthProvider lifecycle hook (set_mcp_path call) into
  the base get_routes so provider subclasses can react to the path
  becoming known. Previously only OAuthAuthorizationServerProvider
  invoked it.
- Bump JWT rejection reason logs (issuer/audience/scopes) from DEBUG to
  WARNING. The prior 'Bearer token rejected' line was uninformative,
  which cost a debugging round-trip during development.
- Rename examples/auth/authkit_dcr -> examples/auth/authkit (DCR-vs-CIMD
  is a Dashboard choice, not an example-level distinction) and rewrite
  the README around the Dashboard setup.
- Normalize all auth examples to 127.0.0.1 for host consistency between
  server base_url and client SERVER_URL; prevents audience mismatches
  for any provider that gains RFC 8707 support later.
@marvin-context-protocol marvin-context-protocol Bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. auth Related to authentication (Bearer, JWT, OAuth, WorkOS) for client or server. security Security fixes: input validation, SSRF/LFI prevention, auth hardening, injection defenses. labels Apr 13, 2026
- Fold the expiry-rejection log into a single INFO line with an explicit
  comment noting why it intentionally stays quieter than the issuer/
  audience/scope WARNINGs (token rotation is expected-path noise, not a
  configuration error worth surfacing by default).
- Move JWTVerifier import to module level in test_workos.py per the
  project's 'all imports at top' rule.
- Add test for set_mcp_path(None): documents the MCP-at-root contract
  where audience binds to base_url itself.
- Tighten AuthKitProvider docstring: 'at startup' -> 'when the MCP
  mount path becomes known (typically at http_app() construction)'.
@jlowin jlowin removed the breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. label Apr 13, 2026
@jlowin jlowin merged commit 82f310f into main Apr 13, 2026
10 checks passed
@jlowin jlowin deleted the docs/authkit-rfc-8707 branch April 13, 2026 21:11
@jlowin jlowin added the breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. label Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth Related to authentication (Bearer, JWT, OAuth, WorkOS) for client or server. breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. enhancement Improvement to existing functionality. For issues and smaller PR improvements. security Security fixes: input validation, SSRF/LFI prevention, auth hardening, injection defenses.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant