This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch fix/graphql-websocket-auth in repository https://gitbox.apache.org/repos/asf/unomi.git
commit 5ca094dde7c95dad8fe4ace34b85c24eb40e78e3 Author: Serge Huber <[email protected]> AuthorDate: Thu Sep 3 19:39:30 2026 +0200 Authenticate the GraphQL playground's subscription WebSocket The shipped GraphiQL playground opened its subscription WebSocket with only a URL, so it presented no credential over a handshake that authenticates from the connection_init payload, leaving subscriptions unusable from the browser. The browser WebSocket API cannot set request headers on the upgrade, so reuse the Authorization the operator enters in GraphiQL's Headers tab instead: GraphiQL hands that editor's live content to the fetcher on every request, so capture it there and return it as the graphql-ws connection parameters, which are sent in the connection_init payload. HTTP and WebSocket then authenticate with the same credential, and it stays in memory rather than being written to browser storage. Co-Authored-By: Claude Opus 4.8 <[email protected]> --- .../src/main/resources/assets/js/index.jsx | 30 ++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/graphql/graphql-ui/src/main/resources/assets/js/index.jsx b/graphql/graphql-ui/src/main/resources/assets/js/index.jsx index a0869796f..a1de4467e 100644 --- a/graphql/graphql-ui/src/main/resources/assets/js/index.jsx +++ b/graphql/graphql-ui/src/main/resources/assets/js/index.jsx @@ -33,11 +33,37 @@ function graphqlWsUrl() { return protocol + '//' + window.location.host + '/graphql'; } +// The browser WebSocket API cannot set request headers on the handshake, so the server authenticates a +// subscription from the connection_init payload instead. GraphiQL hands the live "Headers" tab content +// to the fetcher on every request, so capture it here and reuse its Authorization as the WebSocket +// connection parameters: HTTP and WebSocket then use the same credential, and nothing is persisted. +let latestHeaders = null; + +function authorizationHeader() { + if (!latestHeaders) { + return null; + } + const key = Object.keys(latestHeaders).find((name) => name.toLowerCase() === 'authorization'); + return key && latestHeaders[key] ? latestHeaders[key] : null; +} + function createFetcher() { - return createGraphiQLFetcher({ + const fetcher = createGraphiQLFetcher({ url: graphqlHttpUrl(), - wsClient: createClient({ url: graphqlWsUrl() }), + wsClient: createClient({ + url: graphqlWsUrl(), + // Evaluated on each (re)connect, and sent as the connection_init payload. + connectionParams: () => { + const authorization = authorizationHeader(); + return authorization ? { Authorization: authorization } : {}; + }, + }), }); + + return (graphQLParams, opts) => { + latestHeaders = (opts && opts.headers) || null; + return fetcher(graphQLParams, opts); + }; } function QueryPlayground() {
