From db92a0a3246d7c1c5aa2a5c265bf7773afa528da Mon Sep 17 00:00:00 2001 From: vincbeck Date: Thu, 6 Nov 2025 17:40:34 -0500 Subject: [PATCH] Fix logout in airflow-core --- .../api_fastapi/core_api/routes/public/auth.py | 17 +++++++++++++---- .../airflow/ui/src/layouts/Nav/LogoutModal.tsx | 1 - .../core_api/routes/public/test_auth.py | 13 ++++++++++--- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/auth.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/auth.py index 59610e0fc9469..08250e458b976 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/auth.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/auth.py @@ -19,9 +19,11 @@ from fastapi import HTTPException, Request, status from fastapi.responses import RedirectResponse +from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN from airflow.api_fastapi.common.router import AirflowRouter from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_exception_doc from airflow.api_fastapi.core_api.security import AuthManagerDep, is_safe_url +from airflow.configuration import conf auth_router = AirflowRouter(tags=["Login"], prefix="/auth") @@ -47,11 +49,18 @@ def login(request: Request, auth_manager: AuthManagerDep, next: None | str = Non "/logout", responses=create_openapi_http_exception_doc([status.HTTP_307_TEMPORARY_REDIRECT]), ) -def logout(auth_manager: AuthManagerDep, next: None | str = None) -> RedirectResponse: +def logout(request: Request, auth_manager: AuthManagerDep, next: None | str = None) -> RedirectResponse: """Logout the user.""" logout_url = auth_manager.get_url_logout() + if logout_url: + return RedirectResponse(logout_url) - if not logout_url: - logout_url = auth_manager.get_url_login() + secure = request.base_url.scheme == "https" or bool(conf.get("api", "ssl_cert", fallback="")) + response = RedirectResponse(auth_manager.get_url_login()) + response.delete_cookie( + key=COOKIE_NAME_JWT_TOKEN, + secure=secure, + httponly=True, + ) - return RedirectResponse(logout_url) + return response diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/LogoutModal.tsx b/airflow-core/src/airflow/ui/src/layouts/Nav/LogoutModal.tsx index ed58d2f23535c..6ad8226c22be1 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Nav/LogoutModal.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Nav/LogoutModal.tsx @@ -37,7 +37,6 @@ const LogoutModal: React.FC = ({ isOpen, onClose }) => { onConfirm={() => { const logoutPath = getRedirectPath("api/v2/auth/logout"); - document.cookie = "_token=; Path=/; Max-Age=-99999999;"; globalThis.location.replace(logoutPath); }} onOpenChange={onClose} diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_auth.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_auth.py index 8697cc75b56b4..297e66e5ae854 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_auth.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_auth.py @@ -20,6 +20,8 @@ import pytest +from airflow.api_fastapi.auth.managers.base_auth_manager import COOKIE_NAME_JWT_TOKEN + from tests_common.test_utils.config import conf_vars AUTH_MANAGER_LOGIN_URL = "http://some_login_url" @@ -74,12 +76,12 @@ def test_should_respond_400(self, test_client, params): class TestLogout(TestAuthEndpoint): @pytest.mark.parametrize( - "mock_logout_url, expected_redirection", + "mock_logout_url, expected_redirection, delete_cookies", [ # logout_url is None, should redirect to the login page directly. - (None, AUTH_MANAGER_LOGIN_URL), + (None, AUTH_MANAGER_LOGIN_URL, True), # logout_url is defined, should redirect to the logout_url. - ("http://localhost/auth/some_logout_url", "http://localhost/auth/some_logout_url"), + ("http://localhost/auth/some_logout_url", "http://localhost/auth/some_logout_url", False), ], ) def test_should_respond_307( @@ -87,9 +89,14 @@ def test_should_respond_307( test_client, mock_logout_url, expected_redirection, + delete_cookies, ): test_client.app.state.auth_manager.get_url_logout.return_value = mock_logout_url response = test_client.get("/auth/logout", follow_redirects=False) assert response.status_code == 307 assert response.headers["location"] == expected_redirection + + if delete_cookies: + cookies = response.headers.get_list("set-cookie") + assert any(f"{COOKIE_NAME_JWT_TOKEN}=" in c for c in cookies)