MJ-06 opened a new issue, #40933:
URL: https://github.com/apache/superset/issues/40933

   ### Bug description
   
   # Bug: Streaming CSV export fails in embedded dashboards using guest token 
authentication
   
   ## Description
   
   The new streaming CSV export feature (introduced in PR #35478) fails with 
HTTP 401 (`Missing Authorization Header`) when triggered from an embedded 
Superset dashboard that uses guest token authentication.
   
   The root cause is that `useStreamingExport.ts` uses a raw `fetch()` call 
that bypasses `SupersetClient`, so neither the `Authorization` header nor the 
`guest_token` form field is included in the request.
   
   The non-streaming (legacy) CSV export path uses `SupersetClient.postForm()`, 
which correctly injects `guest_token` into the form data (fixed by PR #20261 in 
2022). The streaming export implementation was added later and missed this 
behavior.
   
   ---
   
   ## How to Reproduce
   
   1. Configure Superset with embedded dashboards enabled:
   
   ```python
   EMBEDDED_SUPERSET = True
   ```
   
   2. Ensure the guest role has CSV export permissions (`can_csv`).
   
   3. Embed a dashboard using `@superset-ui/embedded-sdk` and `fetchGuestToken`.
   
   4. Navigate to a chart whose dataset exceeds `CSV_STREAMING_ROW_THRESHOLD` 
(default: `100000` rows).
   
   5. Click:
   
   ```
   Download → Export to CSV
   ```
   
   6. Observe that the streaming export modal appears and then immediately 
fails.
   
   ---
   
   ## Expected Results
   
   Streaming CSV export should complete successfully in embedded mode, just as 
it does when accessing Superset directly using session-based authentication.
   
   ---
   
   ## Actual Results
   
   The streaming export POST request to:
   
   ```text
   /api/v1/chart/data
   ```
   
   returns:
   
   ```json
   {
     "msg": "Missing Authorization Header"
   }
   ```
   
   and the UI displays:
   
   ```text
   Export failed: 401
   ```
   
   ---
   
   ## Root Cause Analysis
   
   ### Streaming export path
   
   `useStreamingExport.ts` → `createFetchRequest()`
   
   ```ts
   const headers: Record<string, string> = {
     'Content-Type': 'application/x-www-form-urlencoded',
   };
   
   const csrfToken = await SupersetClient.getCSRFToken();
   
   if (csrfToken) {
     headers['X-CSRFToken'] = csrfToken;
   }
   
   return {
     method: 'POST',
     headers,
     body: new URLSearchParams(formParams),
     signal,
     credentials: 'same-origin',
   };
   ```
   
   The request is then executed using:
   
   ```ts
   const response = await fetch(url, fetchOptions);
   ```
   
   Because this is a raw `fetch()` call:
   
   * No `Authorization` header is added.
   * No `guest_token` is added to the form payload.
   * Embedded authentication therefore fails.
   
   ### Working legacy export path
   
   `SupersetClientClass.ts` → `postForm()`
   
   ```ts
   if (this.guestToken) {
     formData.guest_token = this.guestToken;
   }
   ```
   
   This behavior was introduced by PR #20261 and allows embedded dashboards to 
authenticate correctly.
   
   ### Backend already supports guest_token in form data
   
   `security/manager.py` → `get_guest_user_from_request()`
   
   ```python
   raw_token = req.headers.get(
       get_conf()["GUEST_TOKEN_HEADER_NAME"]
   ) or req.form.get("guest_token")
   ```
   
   Therefore no backend changes are required.
   
   ---
   
   ## Proposed Fix
   
   Add `guest_token` to the form body in `useStreamingExport.ts`, matching the 
behavior already implemented in `SupersetClient.postForm()`.
   
   ```ts
   // After building formParams
   const guestToken = SupersetClient.getGuestToken?.();
   
   if (guestToken) {
     formParams.guest_token = guestToken;
   }
   ```
   
   This is the minimal and backward-compatible fix because the backend already 
accepts `guest_token` from form data.
   
   ---
   
   ## Environment
   
   * Superset version: `6.1.0`
   * Also reproduced on: `6.1.0rc3`
   * Deployment: Kubernetes (Helm chart)
   * Embedded SDK: `@superset-ui/embedded-sdk`
   * Browser: Chrome 130+
   
   ---
   
   ## Workaround
   
   Setting:
   
   ```python
   CSV_STREAMING_ROW_THRESHOLD = 0
   ```
   
   only partially mitigates the issue.
   
   Reason:
   
   1. Frontend logic treats `0` as falsy (`0 || 100000`), so the client-side 
threshold still becomes `100000`.
   2. Exports below `100000` rows use the legacy export path 
(`SupersetClient.postForm()`), which works.
   3. Exports at or above `100000` rows still use the streaming export path and 
fail with HTTP 401.
   
   ---
   
   ## Related Issues / PRs
   
   * #20261 — Embedded CSV download guest token fix (2022)
   * #35478 — Streaming CSV export feature (introduced regression)
   * #38513 — CSV/Excel download fixes for legacy chart types
   * #36950 — Streaming export context (`g`) loss issue (separate bug)
   
   ---
   
   ## Checklist
   
   * [x] I have searched existing issues and this is not a duplicate.
   * [x] I have provided reproduction steps.
   * [x] I have included relevant environment information.
   * [x] I have identified the likely root cause.
   * [x] I have proposed a minimal fix.
   
   
   ### Screenshots/recordings
   
   <img width="531" height="230" alt="Image" 
src="https://github.com/user-attachments/assets/cb7999be-f5ba-4403-ac7e-4396a00829a3";
 />
   <img width="565" height="854" alt="Image" 
src="https://github.com/user-attachments/assets/a2a055eb-f3bb-415a-9a3e-33e6a0a2faee";
 />
   
   ### Superset version
   
   master / latest-dev
   
   ### Python version
   
   3.9
   
   ### Node version
   
   16
   
   ### Browser
   
   Chrome
   
   ### Additional context
   
   
   **Environment:** Set the dropdowns to:
   - Superset version: **`6.1.0`** (select from dropdown or type)
   - Python: **3.9**
   - Node: **16**
   - Browser: **Chrome**
   
   ```markdown
   The fix is a ~3 line change in `useStreamingExport.ts` to read the guest 
token from `SupersetClient` and add it to `formParams.guest_token`, matching 
what `SupersetClient.postForm()` already does. The backend 
`get_guest_user_from_request()` in `security/manager.py` already handles 
`request.form.get("guest_token")`.
   
   ### 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.


-- 
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]

Reply via email to