apache/superset · critical · AsyncQueryTokenException
Please provide a JWT secret at least 32 bytes long
Error message
Please provide a JWT secret at least 32 bytes long
What it means
AsyncQueryTokenException raised during AsyncQueryManager.init_app() when GLOBAL_ASYNC_QUERIES_JWT_SECRET is shorter than 32 bytes. The JWT secret signs the per-user channel tokens stored in a cookie that authorize access to the Redis event stream, so a short secret would be brute-forceable; Superset enforces a minimum length at startup.
Source
Thrown at superset/async_events/async_query_manager.py:144
self._load_chart_data_into_cache_job: Any = None
# pylint: disable=invalid-name
def init_app(self, app: Flask) -> None:
cache_type = app.config.get("CACHE_CONFIG", {}).get("CACHE_TYPE")
data_cache_type = app.config.get("DATA_CACHE_CONFIG", {}).get("CACHE_TYPE")
if cache_type in [None, "null"] or data_cache_type in [None, "null"]:
raise Exception( # pylint: disable=broad-exception-raised
"""
Cache backends (CACHE_CONFIG, DATA_CACHE_CONFIG) must be configured
and non-null in order to enable async queries
"""
)
self._cache = get_cache_backend(app.config)
logger.debug("Using GAQ Cache backend as %s", type(self._cache).__name__)
if len(app.config["GLOBAL_ASYNC_QUERIES_JWT_SECRET"]) < 32:
raise AsyncQueryTokenException(
"Please provide a JWT secret at least 32 bytes long"
)
self._stream_prefix = app.config["GLOBAL_ASYNC_QUERIES_REDIS_STREAM_PREFIX"]
self._stream_limit = app.config["GLOBAL_ASYNC_QUERIES_REDIS_STREAM_LIMIT"]
self._stream_limit_firehose = app.config[
"GLOBAL_ASYNC_QUERIES_REDIS_STREAM_LIMIT_FIREHOSE"
]
self._jwt_cookie_name = app.config["GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME"]
self._jwt_cookie_secure = app.config["GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE"]
self._jwt_cookie_samesite = app.config[
"GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE"
]
self._jwt_cookie_domain = app.config["GLOBAL_ASYNC_QUERIES_JWT_COOKIE_DOMAIN"]
self._jwt_secret = app.config["GLOBAL_ASYNC_QUERIES_JWT_SECRET"]
self._jwt_expiration_seconds = app.config[
"GLOBAL_ASYNC_QUERIES_JWT_EXPIRATION_SECONDS"
]View on GitHub (pinned to f4587218dd)
Solutions
- Generate a proper secret: python -c "import secrets; print(secrets.token_urlsafe(64))" and set GLOBAL_ASYNC_QUERIES_JWT_SECRET to it (>= 32 chars).
- Store it in a secret manager / environment variable rather than hardcoding, then reference it in superset_config.py.
- Set the same secret on webserver and all Celery workers — tokens are signed and verified across processes.
Example fix
# before GLOBAL_ASYNC_QUERIES_JWT_SECRET = "supersecret" # after import os GLOBAL_ASYNC_QUERIES_JWT_SECRET = os.environ["SUPERSET_JWT_SECRET"] # >= 32 chars, e.g. secrets.token_urlsafe(64)
Defensive patterns
Strategy: validation
Validate before calling
import os, secrets
secret = os.environ.get("GLOBAL_ASYNC_QUERIES_JWT_SECRET", "")
if len(secret.encode()) < 32:
secret = secrets.token_urlsafe(64)
# persist via your secret manager; never fall back silently in production
assert len(secret.encode()) >= 32 Prevention
- Provision the JWT secret alongside other secrets in IaC with a minimum-length policy.
- Distribute the same secret to webserver and workers via a shared secret store.
When it happens
Trigger: Enabling GLOBAL_ASYNC_QUERIES with GLOBAL_ASYNC_QUERIES_JWT_SECRET set to a short string (e.g. 'secret' or a 16-char value), or leaving it unset so an empty/default value is checked. Raised at init_app time, blocking app and worker startup.
Common situations: Copy-pasting example config with a placeholder secret; generating a secret with too few characters; upgrading deployments where the secret was previously optional; CI configs that reuse a short dummy secret.
Related errors
- Unsupported cache backend configuration
- Cache backends (CACHE_CONFIG, DATA_CACHE_CONFIG) must be con
- Failed to parse token
- Token not preset
- {ex}
AI-assisted analysis of apache/superset@f4587218dd (2026-08-14).
Data as JSON: /api/errors/f8500bac8df09727.
Report an issue: GitHub.