FAB Security views (Add/Edit/Show role/user links) miss /auth prefix when running behind a subpath reverse proxy (enable_proxy_fix + X-Forwarded-Prefix)
- Dominant language
- Python
- Stars
- 46.9k
- Forks
- 17.8k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 472
Description
### Apache Airflow version
3.3.0
### What happened?
When Airflow is deployed behind a reverse proxy under a URL subpath (a documented/supported pattern using `[fab] enable_proxy_fix = True` + `[api] base_url` + the proxy sending `X-Forwarded-Prefix`), the FAB-rendered Security pages (`/auth/roles/list/`, `/auth/users/list/`) generate "Add" / "Edit" / "Show" action links that are **missing the `/auth` blueprint prefix**.
For example, with subpath `/myns/myrelease`:
- Expected: `/myns/myrelease/auth/roles/add`
- Actual: `/myns/myrelease/roles/add`
Since the FAB blueprint is mounted under `/auth`, the broken link (missing `/auth`) resolves instead to the new React SPA shell, which has no client-side route for `roles`/`users` paths and silently redirects/bounces the user back to the Dashboard — with no visible error. This makes it effectively impossible to add/edit/view Roles or Users from the UI when Airflow is served under a subpath.
Notably, the List/Edit page URLs themselves (e.g. `/auth/roles/list/`) are correctly generated with the `/auth` prefix — it's specifically the row/page "Add", "Edit", and "Show" action links produced by FAB's ModelView machinery that drop the prefix, and only when `X-Forwarded-Prefix`/ProxyFix subpath handling is active.
### What you think should happen instead?
The "Add"/"Edit"/"Show" action links on FAB Security views should include the `/auth` blueprint prefix (consistent with the page's own list/edit URLs), regardless of whether the app is served under a subpath via ProxyFix/`X-Forwarded-Prefix`.
### How to reproduce
Minimal, clean-room repro with **no custom Helm chart / ingress / Istio** involved — plain `apache/airflow:3.3.0-python3.12` Docker image, FAB auth manager, default `webserver_config.py` with `AUTH_TYPE = AUTH_DB`.
**Control (no subpath) — works correctly:**
```bash
docker run -d --name airflow-repro -p 18080:8080 \
-e AIRFLOW__CORE__AUTH_MANAGER=airflow.providers.fab.auth_manager.fab_auth_manager.FabAuthManager \
-e AIRFLOW__CORE__EXECUTOR=LocalExecutor \
-e AIRFLOW__DATABASE__SQL_ALCHEMY_CONN=sqlite:////opt/airflow/airflow.db \
-v /path/to/webserver_config.py:/opt/airflow/webserver_config.py:ro \
apache/airflow:3.3.0-python3.12 bash -c "airflow db migrate && (airflow scheduler &) && airflow api-server --port 8080"
docker exec airflow-repro airflow users create --username admin --firstname A --lastname B \
--email admin@example.com --role Admin --password admin
```
Log in at `http://localhost:18080/auth/login/`, navigate to `http://localhost:18080/auth/roles/list/`.
The "Add" button's href is `/auth/roles/add` — correct.
**Subpath repro (same image, add these two envs, and simulate a reverse proxy sending `X-Forwarded-Prefix`):**
```bash
docker run -d --name airflow-repro -p 18080:8080 \
-e AIRFLOW__CORE__AUTH_MANAGER=airflow.providers.fab.auth_manager.fab_auth_manager.FabAuthManager \
-e AIRFLOW__CORE__EXECUTOR=LocalExecutor \
-e AIRFLOW__DATABASE__SQL_ALCHEMY_CONN=sqlite:////opt/airflow/airflow.db \
-e AIRFLOW__FAB__ENABLE_PROXY_FIX=True \
-e AIRFLOW__API__BASE_URL=https://example.com/myns/myrelease \
-v /path/to/webserver_config.py:/opt/airflow/webserver_config.py:ro \
apache/airflow:3.3.0-python3.12 bash -c "airflow db migrate && (airflow scheduler &) && airflow api-server --port 8080"
```
Then make requests with an added header `X-Forwarded-Prefix: /myns/myrelease` (e.g. via a browser extension, a small proxy, or Playwright request interception) while browsing `http://localhost:18080/myns/myrelease/auth/roles/list/`.
The "Add" button's href is now `/myns/myrelease/roles/add` — **missing `/auth`**. Navigating to it loads the SPA shell instead of the FAB add-role form, and the SPA immediately redirects to the Dashboard since it has no client-side route matching `roles`/`users` paths.
Same image, same FAB version — only the proxy-fix/subpath config differs between the working and broken case.
### Operating System
Debian (apache/airflow official Docker image base), also reproduced on AKS/Kubernetes with Istio ingress
### Versions of Apache Airflow Providers
apache-airflow-providers-fab (bundled with Airflow 3.3.0)
### Deployment
Other Docker-based deployment
### Deployment details
Also reproduced in a Kubernetes deployment behind an Istio Ingress Gateway/VirtualService, with `[fab] enable_proxy_fix = True` and `[api] base_url` set to the subpath, and `X-Forwarded-Prefix` set by the ingress. Confirmed the ingress/VirtualService routing itself is not the cause (verified byte-identical FAB static assets, correct base_url resolution, correct ProxyFix redirect behavior for unauthenticated pages) — isolated the bug down to just the two Airflow-side config values (`enable_proxy_fix` + `base_url`/`X-Forwarded-Prefix`) via the clean-room Docker repro above.
### Anything else?
This will affect any Airflow 3.x deployment using the FAB auth manager behind a reverse proxy serving Airflow under a URL subpath — a pattern explicitly documented and supported via `[fab] enable_proxy_fix`. Since the failure mode is a silent redirect to the Dashboard with no error message, it may be underreported/hard to notice at first (it looks like the button "does nothing").
### Are you willing to submit a PR?
- [ ] Yes I am willing to submit a PR!
### Code of Conduct
- [x] I agree to follow this project's Code of Conduct
Contributor guide
Research direction
Reproduce the issue with the documented Docker setup, FAB auth manager, enable_proxy_fix, base_url, and X-Forwarded-Prefix. Start at FAB's ModelView machinery that generates Security-page Add, Edit, and Show links; done means those links retain the /auth prefix under a subpath while the no-subpath control case remains correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- authentication, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100