From c113fe3280124f3049bde2ff700f6dedea86b0ac Mon Sep 17 00:00:00 2001 From: lyric2249 Date: Sun, 12 Jul 2026 10:01:27 +0000 Subject: [PATCH 1/5] Fix AUTH_ROLE_PUBLIC returning 401 in FastAPI API server FabAuthRolePublicMiddleware only sets request.state.user for anonymous requests. Core's get_user() dependency also checks request.state.user_authenticated_via for the trusted-middleware sentinel before skipping JWT validation, so every anonymous request falls through to JWT validation, has no token, and is rejected with 401 regardless of AUTH_ROLE_PUBLIC (or [fab] auth_role_public) being configured correctly. Set the same trust marker JWTRefreshMiddleware already sets when it pre-authenticates a request, so both middlewares honor the same contract. --- .../fab/src/airflow/providers/fab/auth_manager/middleware.py | 2 ++ providers/fab/tests/unit/fab/auth_manager/test_middleware.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py index 3bf2cea833c2f..e97168be9cb88 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py @@ -23,6 +23,7 @@ from airflow.api_fastapi.app import get_auth_manager from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN +from airflow.api_fastapi.core_api.security import USER_INJECTED_BY_TRUSTED_MIDDLEWARE if TYPE_CHECKING: from fastapi import Request @@ -47,6 +48,7 @@ async def dispatch(self, request: Request, call_next): public_user = auth_manager.build_public_user() if public_user is not None: request.state.user = public_user + request.state.user_authenticated_via = USER_INJECTED_BY_TRUSTED_MIDDLEWARE return await call_next(request) @staticmethod diff --git a/providers/fab/tests/unit/fab/auth_manager/test_middleware.py b/providers/fab/tests/unit/fab/auth_manager/test_middleware.py index e4117a0fe9198..6acaf5f8eaabd 100644 --- a/providers/fab/tests/unit/fab/auth_manager/test_middleware.py +++ b/providers/fab/tests/unit/fab/auth_manager/test_middleware.py @@ -21,6 +21,7 @@ import pytest from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN +from airflow.api_fastapi.core_api.security import USER_INJECTED_BY_TRUSTED_MIDDLEWARE from airflow.providers.fab.auth_manager.middleware import FabAuthRolePublicMiddleware @@ -47,6 +48,7 @@ async def test_sets_public_user_when_no_auth_present(self, mock_get_auth_manager await middleware.dispatch(request, call_next) assert request.state.user is public_user + assert request.state.user_authenticated_via is USER_INJECTED_BY_TRUSTED_MIDDLEWARE auth_manager.build_public_user.assert_called_once_with() call_next.assert_awaited_once_with(request) From ac708eb543ae219ae6453612e9eb2836af286b77 Mon Sep 17 00:00:00 2001 From: John Doe <55012088+lyric2249@users.noreply.github.com> Date: Sun, 12 Jul 2026 22:45:44 +0900 Subject: [PATCH 2/5] Fix FAB middleware compatibility with Airflow<3.3 The FAB provider supports Airflow 3.0.2 and later, while USER_INJECTED_BY_TRUSTED_MIDDLEWARE is only available in newer core releases. Importing it unconditionally prevents the provider from loading and causes test collection to fail against supported versions such as Airflow 3.0.6. Preserve trusted middleware authentication on newer core versions without requiring older supported versions to expose the marker. r Airflow versions --- .../providers/fab/auth_manager/middleware.py | 15 +++++++++++++-- .../unit/fab/auth_manager/test_middleware.py | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py index e97168be9cb88..289026d2b19ce 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py @@ -23,7 +23,13 @@ from airflow.api_fastapi.app import get_auth_manager from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN -from airflow.api_fastapi.core_api.security import USER_INJECTED_BY_TRUSTED_MIDDLEWARE +from airflow.api_fastapi.core_api import security as core_api_security + +_USER_INJECTED_BY_TRUSTED_MIDDLEWARE = getattr( + core_api_security, + "USER_INJECTED_BY_TRUSTED_MIDDLEWARE", + None, +) if TYPE_CHECKING: from fastapi import Request @@ -48,7 +54,12 @@ async def dispatch(self, request: Request, call_next): public_user = auth_manager.build_public_user() if public_user is not None: request.state.user = public_user - request.state.user_authenticated_via = USER_INJECTED_BY_TRUSTED_MIDDLEWARE + + if _USER_INJECTED_BY_TRUSTED_MIDDLEWARE is not None: + request.state.user_authenticated_via = ( + _USER_INJECTED_BY_TRUSTED_MIDDLEWARE + ) + return await call_next(request) @staticmethod diff --git a/providers/fab/tests/unit/fab/auth_manager/test_middleware.py b/providers/fab/tests/unit/fab/auth_manager/test_middleware.py index 6acaf5f8eaabd..b6dc2665531d8 100644 --- a/providers/fab/tests/unit/fab/auth_manager/test_middleware.py +++ b/providers/fab/tests/unit/fab/auth_manager/test_middleware.py @@ -21,7 +21,7 @@ import pytest from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN -from airflow.api_fastapi.core_api.security import USER_INJECTED_BY_TRUSTED_MIDDLEWARE +from airflow.api_fastapi.core_api import security as core_api_security from airflow.providers.fab.auth_manager.middleware import FabAuthRolePublicMiddleware @@ -48,7 +48,18 @@ async def test_sets_public_user_when_no_auth_present(self, mock_get_auth_manager await middleware.dispatch(request, call_next) assert request.state.user is public_user - assert request.state.user_authenticated_via is USER_INJECTED_BY_TRUSTED_MIDDLEWARE + + trusted_marker = getattr( + core_api_security, + "USER_INJECTED_BY_TRUSTED_MIDDLEWARE", + None, + ) + + if trusted_marker is not None: + assert request.state.user_authenticated_via is trusted_marker + else: + assert not hasattr(request.state, "user_authenticated_via") + auth_manager.build_public_user.assert_called_once_with() call_next.assert_awaited_once_with(request) From eb51ac986d6458b174457cef63765b4a57b5aa3a Mon Sep 17 00:00:00 2001 From: John Doe <55012088+lyric2249@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:04:37 +0900 Subject: [PATCH 3/5] fix: reflect modifications based on comments --- .../providers/fab/auth_manager/middleware.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py index 289026d2b19ce..df723297855f5 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py @@ -25,11 +25,7 @@ from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN from airflow.api_fastapi.core_api import security as core_api_security -_USER_INJECTED_BY_TRUSTED_MIDDLEWARE = getattr( - core_api_security, - "USER_INJECTED_BY_TRUSTED_MIDDLEWARE", - None, -) + if TYPE_CHECKING: from fastapi import Request @@ -51,14 +47,18 @@ class FabAuthRolePublicMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): if not self._has_auth_token(request): auth_manager = cast("FabAuthManager", get_auth_manager()) + public_user = auth_manager.build_public_user() if public_user is not None: request.state.user = public_user - if _USER_INJECTED_BY_TRUSTED_MIDDLEWARE is not None: - request.state.user_authenticated_via = ( - _USER_INJECTED_BY_TRUSTED_MIDDLEWARE - ) + user_injected = getattr( + core_api_security, + "USER_INJECTED_BY_TRUSTED_MIDDLEWARE", + None, + ) + if user_injected is not None: + request.state.user_authenticated_via = user_injected return await call_next(request) From 4107076452c004dd82ef342c9e2a7d44ecec29ee Mon Sep 17 00:00:00 2001 From: John Doe <55012088+lyric2249@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:51:45 +0900 Subject: [PATCH 4/5] fix: reflect modifications based on comments --- .../providers/fab/auth_manager/middleware.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py index df723297855f5..59f8d362862a2 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py @@ -52,13 +52,13 @@ async def dispatch(self, request: Request, call_next): if public_user is not None: request.state.user = public_user - user_injected = getattr( - core_api_security, - "USER_INJECTED_BY_TRUSTED_MIDDLEWARE", - None, - ) - if user_injected is not None: - request.state.user_authenticated_via = user_injected + user_injected = getattr( + core_api_security, + "USER_INJECTED_BY_TRUSTED_MIDDLEWARE", + None, + ) + if user_injected is not None: + request.state.user_authenticated_via = user_injected return await call_next(request) From 1a2690bbc44bfac4848bf45d9dc02036e0033164 Mon Sep 17 00:00:00 2001 From: Elad Kalif <45845474+eladkal@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:16:59 -0400 Subject: [PATCH 5/5] Update providers/fab/src/airflow/providers/fab/auth_manager/middleware.py --- .../fab/src/airflow/providers/fab/auth_manager/middleware.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py index 59f8d362862a2..e16b1af85bba6 100644 --- a/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py +++ b/providers/fab/src/airflow/providers/fab/auth_manager/middleware.py @@ -25,8 +25,6 @@ from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN from airflow.api_fastapi.core_api import security as core_api_security - - if TYPE_CHECKING: from fastapi import Request