rsprudencio opened a new pull request, #37862:
URL: https://github.com/apache/superset/pull/37862
### SUMMARY
When `GLOBAL_ASYNC_QUERIES` is enabled and `get_user_id()` returns `None`
(guest users, embedded dashboards, session expiry), the `validate_session`
after-request handler creates JWT tokens with `sub: None`. PyJWT 2.10.1's
strict RFC 7519 validation requires `sub` to be a string when present,
causing `InvalidSubjectError` on decode in `parse_channel_id_from_request()`
— resulting in `AsyncQueryTokenException` and 401 failures for all
subsequent async queries.
**Fix:** Conditionally include the `sub` claim only when `user_id` is not
`None`. RFC 7519 specifies `sub` as optional, so omitting it entirely is
valid and PyJWT skips validation for missing claims.
```python
# Before (buggy):
sub = str(user_id) if user_id else None
token = jwt.encode({"channel": async_channel_id, "sub": sub}, ...)
# After (fixed):
payload = {"channel": async_channel_id}
if user_id:
payload["sub"] = str(user_id)
token = jwt.encode(payload, ...)
Fixes: #34696 #34611 #31492 #33561 #34337 #32219
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — backend JWT handling change, no UI impact.
TESTING INSTRUCTIONS
Prerequisites: A running Superset dev environment with GLOBAL_ASYNC_QUERIES
= True in superset_config.py.
Reproduce the bug (on master, before fix):
docker compose exec -T superset python3 << 'EOF'
from superset.app import create_app
from superset.async_events.async_query_manager import AsyncQueryManager
from unittest.mock import patch
from flask import request
app = create_app()
with app.app_context():
mgr = AsyncQueryManager()
mgr.register_request_handlers(app)
mgr._jwt_secret = app.config["GLOBAL_ASYNC_QUERIES_JWT_SECRET"]
mgr._jwt_cookie_name =
app.config.get("GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME", "async_access")
mgr._jwt_cookie_secure = False
mgr._jwt_cookie_domain = None
mgr._jwt_cookie_samesite = "Lax"
# Step 1: Guest user request → after_request creates JWT cookie
with patch("superset.async_events.async_query_manager.get_user_id",
return_value=None):
resp = app.test_client().get("/health")
token = [v for k, v in resp.headers if k == "Set-Cookie" and
mgr._jwt_cookie_name in v][0]
token = token.split("=", 1)[1].split(";")[0]
# Step 2: Next request with that cookie → real code decodes it
with app.test_request_context(headers={"Cookie":
f"{mgr._jwt_cookie_name}={token}"}):
try:
channel = mgr.parse_channel_id_from_request(request)
print(f"✅ Fix works! Channel: {channel}")
except Exception as e:
print(f"🐛 Bug! {e}")
EOF
```
Expected on master: 🐛 Bug! Failed to parse token (caused by
InvalidSubjectError: Subject must be a string at line 204)
Expected with this fix: ✅ Fix works! Channel: <uuid>
The test exercises the real code path end-to-end: validate_session
(after_request handler) creates the JWT, then parse_channel_id_from_request
decodes it — no manual jwt.encode/decode in the test.
ADDITIONAL INFORMATION
- Has associated issue: #34696, #34611, #31492, #33561, #34337, #32219
- Required feature flags: None (GLOBAL_ASYNC_QUERIES config option
required to test)
- Changes UI: No
- Includes DB Migration: No
- Introduces new feature or API: No
- Removes existing feature or API: No
CHECKLIST
- PR title follows Conventional Commits format
- Backward compatible — no behavior change for authenticated users
- Minimal change: 7 lines changed in one file
- Tests added/updated
- Documentation updated
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]