Three independent bugs let MCP requests presenting Bearer tokens with the
sst_ prefix authenticate as MCP_DEV_USERNAME without any validation under
streamable-http:
1. _resolve_user_from_api_key read the token from flask.request.headers,
but the streamable-http transport never pushes a Flask request context
— has_request_context() was always False, so the function returned
None before validating, falling through to the dev-user fallback.
Now reads the token from FastMCP's per-request AccessToken (which the
CompositeTokenVerifier already populated) and fails closed when the
key is invalid.
2. CompositeTokenVerifier was only installed when MCP_AUTH_ENABLED=True.
With FAB_API_KEY_ENABLED=True alone, no transport-level verifier
existed at all. The factory now builds an API-key-only verifier in
that case (jwt_verifier=None) that rejects non-API-key Bearer tokens
at the transport instead of silently accepting them.
3. The pass-through AccessToken was minted with scopes=[], which would
make FastMCP's RequireAuthMiddleware 403 every API-key request when
MCP_REQUIRED_SCOPES is non-empty. Pass-through now propagates
self.required_scopes.
Also addresses Daniel's review comment on superset/security/manager.py:
adds "ApiKey" to ADMIN_ONLY_VIEW_MENUS so the FAB ApiKeyApi PVMs are
gated to Admin instead of leaking to Alpha and Gamma.
Renames the pass-through claim from _api_key_passthrough to the
namespaced _superset_mcp_api_key_passthrough (exported as
API_KEY_PASSTHROUGH_CLAIM) so a custom claim from an external IdP can't
accidentally divert a JWT into the API-key validation path.
Tests updated to mock get_access_token instead of app.test_request_context
(the simulated Flask context was the reason the prior tests passed while
production failed). New tests cover API-key-only verifier mode, scope
propagation on pass-through, and the namespaced-claim isolation.
Wire CompositeTokenVerifier into create_default_mcp_auth_factory,
add _api_key_passthrough detection in _resolve_user_from_jwt_context,
create ApiKey permissions in create_custom_permissions, and update
test_auth_api_key with pass-through and non-matching prefix tests.
Two fixes for MCP API key authentication:
1. superset init now creates ApiKey FAB permissions (can_list, can_create,
can_get, can_delete) when FAB_API_KEY_ENABLED=True. Previously, because
Superset uses AppBuilder(update_perms=False), FAB skipped permission
creation during blueprint registration and superset init never picked
them up, causing 403 errors on /api/v1/security/api_keys/.
2. CompositeTokenVerifier allows API key tokens (e.g. sst_...) to coexist
with JWT auth on the MCP transport layer. Previously, when
MCP_AUTH_ENABLED=True, the JWTVerifier rejected all non-JWT Bearer
tokens at the transport layer before they could reach the Flask-level
_resolve_user_from_api_key() handler. The composite verifier detects
API key prefixes and passes them through with a marker claim, letting
the existing auth priority chain handle validation.