Superset Async Query Bug - Websocket Connection Failed JWT not present
Bug description
Hi
I find a bug in the Async Query Websocket code while I'm trying to enable the Async query "ws" mode I followed the instructions and docs to set up all the config values https://github.com/apache/superset/blob/master/CONTRIBUTING.md#async-chart-queries https://github.com/apache/superset/issues/9190
In the websocket code, it is looking for the channel_id from cookie encrypted by JWT https://github.com/apache/superset/blob/9900f566700e6d0a2c376aa4260a0a3f4db27404/superset-websocket/src/index.ts#L278
However in the sueprset_app's ws connection, we are not provbiding proper JWT token to establish WS connection https://github.com/apache/superset/blob/9900f566700e6d0a2c376aa4260a0a3f4db27404/superset-frontend/src/middleware/asyncEvent.ts#L196
Which lead to a JWT not present error.
I try to connects to the Websocket conatainer locally with following code, and it works
const jwt = require('jsonwebtoken');
const WebSocket = require('ws');
// Establish WebSocket connection
const jwts = "*******"
const token = jwt.sign(
{ "channel": "1234122341234" },
jwts,
)
const socket = new WebSocket('ws://127.0.0.1:8080/', {
headers: {
cookie: "async-token=" + token
}
});
// Event listener for when the connection is established
socket.onopen = function (event) {
console.log('WebSocket connection established.');
};
I notice the we create the channel_id and set the cookie in async_mananger, but I don't think it reflects on websocket container https://github.com/apache/superset/blob/9900f566700e6d0a2c376aa4260a0a3f4db27404/superset/async_events/async_query_manager.py#L157
How to reproduce the bug
- follow the instruction to enable Async query
- docker compose up
- you can see the superset_app cannot connect to websocket container because of the JWT token missing
my config GLOBAL_ASYNC_QUERIES_JWT_SECRET = "***********" GLOBAL_ASYNC_QUERIES_TRANSPORT = "ws" GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = True GLOBAL_ASYNC_QUERIES_REDIS_CONFIG = { "port": REDIS_PORT, "host": REDIS_HOST, "password": "", "db": 0, "ssl": False, }
Screenshots/recordings
No response
Superset version
master / latest-dev
Python version
3.9
Node version
16
Browser
Chrome
Additional context
No response
Checklist
- [X] I have searched Superset docs and Slack and didn't find a solution to my problem.
- [X] I have searched the GitHub issue tracker and didn't find a similar bug report.
- [X] I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.
Did you make a copy of superset-websocket/config.example.json as superset-websocket/config.json and change the jwtSecret value? You can use https://jwt.io/ to generate a valid token. I don't think it matters what the payload is, just have to be a valid JWT.
Yeah, I did make the superset-websocket/config.json file with 32 bytes secret key, same key as the GLOBAL_ASYNC_QUERIES_JWT_SECRET I set up in docker/pythonpath_dev/superset_config.py.
In superset app container https://github.com/apache/superset/blob/9900f566700e6d0a2c376aa4260a0a3f4db27404/superset-frontend/src/middleware/asyncEvent.ts#L196
I don't think it provids the JWT in the header, that's why websocket container is giving a JWT not present error
If the token is invalid, it should throw an error with the jwt verification failure in the following line.
https://github.com/apache/superset/blob/9900f566700e6d0a2c376aa4260a0a3f4db27404/superset-websocket/src/index.ts#L282
I am facing the same issue with Kubernetes deployment. JWT
Is anyone still facing this? It seems to have gone silent, so may eventually be closed as stale. In the meantime, the websocket connection is being addressed in a forthcoming SIP by @villebro and subject to architecture redesign.
For good measure, I'll also ping @dosu-bot in case it's helpful here.
Hey @rusackas! I'm here to help you with any bugs, questions, or contributions. I'm Dosu, a bot designed to assist you while you wait for a human maintainer. Let me know how I can help!
To address the "JWT not present" error in the WebSocket connection for Superset's async query feature, ensure the following configurations and steps are correctly implemented:
-
Configuration Settings:
- Ensure that the
GLOBAL_ASYNC_QUERIES_JWT_SECRETandGLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAMEare set correctly in yoursuperset_config.pyand match the values insuperset-websocket/config.json.
- Ensure that the
-
Environment Variables:
- Set the
JWT_SECRETandJWT_COOKIE_NAMEenvironment variables to match the values used in your configuration.
- Set the
-
WebSocket Configuration:
- Ensure the
superset-websocket/config.jsonfile is correctly configured with thejwtSecretandjwtCookieNamevalues. Here is an example configuration:{ "port": 8080, "logLevel": "info", "logToFile": false, "logFilename": "app.log", "statsd": { "host": "127.0.0.1", "port": 8125, "globalTags": [] }, "redis": { "port": 6379, "host": "127.0.0.1", "password": "", "db": 0, "ssl": false }, "redisStreamPrefix": "async-events-", "jwtAlgorithms": ["HS256"], "jwtSecret": "YOUR_32_BYTE_SECRET_KEY", "jwtCookieName": "async-token" }
- Ensure the
-
Session and Cookie Handling:
- The
async_query_manager.pyfile should correctly create thechannel_idand set the JWT cookie. Ensure thevalidate_sessionmethod is properly setting the cookie:def register_request_handlers(self, app: Flask) -> None: @app.after_request def validate_session(response: Response) -> Response: user_id = get_user_id() reset_token = ( not request.cookies.get(self._jwt_cookie_name) or "async_channel_id" not in session or "async_user_id" not in session or user_id != session["async_user_id"] ) if reset_token: async_channel_id = str(uuid.uuid4()) session["async_channel_id"] = async_channel_id session["async_user_id"] = user_id sub = str(user_id) if user_id else None token = jwt.encode( {"channel": async_channel_id, "sub": sub}, self._jwt_secret, algorithm="HS256", ) response.set_cookie( self._jwt_cookie_name, value=token, httponly=True, secure=self._jwt_cookie_secure, domain=self._jwt_cookie_domain, samesite=self._jwt_cookie_samesite, ) return response
- The
-
WebSocket Connection:
- Ensure the WebSocket connection is established with the correct JWT token in the headers. Here is an example of how to manually create a JWT token and establish a WebSocket connection:
const jwt = require('jsonwebtoken'); const WebSocket = require('ws'); const token = jwt.sign( { "channel": "1234122341234" }, "YOUR_32_BYTE_SECRET_KEY" ); const socket = new WebSocket('ws://127.0.0.1:8080/', { headers: { cookie: "async-token=" + token } }); socket.onopen = function (event) { console.log('WebSocket connection established.'); };
- Ensure the WebSocket connection is established with the correct JWT token in the headers. Here is an example of how to manually create a JWT token and establish a WebSocket connection:
-
Check for Updates:
- There have been updates related to WebSocket connections and JWT tokens in Superset. Refer to the pull request #25891 for changes that support different JWT CSRF cookie names, which might address related issues.
By ensuring these configurations and steps are correctly implemented, the "JWT not present" error should be resolved [1][2][3].
To continue talking to Dosu, mention @dosu.
I'm still having the same problem.
I encountered the same problem. When using the Docker Compose UP to build an superset environment, websocket will report to the error of WebSocket Connection Failed JWT not present.
{"level":"error","message":"JWT not present","stack":"Error: JWT not present\n at readChannelId (/home/superset-websocket/dist/index.js:242:15)\n at Server.httpUpgrade (/home/superset-websocket/dist/index.js:337:9)\n at Server.emit (node:events:513:28)\n at onParserExecuteCommon (node:_http_server:772:14)\n at onParserExecute (node:_http_server:686:3)"}
Debug found that JWT Token was not stored in Cookies.
bumping because I am also having this issue on 4.0.2
To resolve this issue, you need to:
- Update the
FEATURE_FLAGSin yoursuperset_config.pyfile - Specifically, set
GLOBAL_ASYNC_QUERIEStoTrueto override the default value insuperset/config.py
This setting is necessary for the token to be set in your cookies - without this change, the tokens will not be set at all
You could check whether this flag is set printing this to console in your browser in your superset app: console.log(window.featureFlags)
Additionally, please review all the points mentioned by @dosubot in the earlier comments, they are still valid, except JWT_SECRET & JWT_COOKIE_NAME