apache / apache/airflow

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)

Open
#70,389 2 comments 0 reactions 0 assignees View on GitHub
area:auth area:UI kind:bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.