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]