This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-2-test by this push:
     new df985453eaa [v3-2-test] Set JWT refresh cookie Secure flag when 
request is HTTPS (#65348) (#65363)
df985453eaa is described below

commit df985453eaacd40e688dc38cb81a4f12b8c8853f
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Apr 16 12:00:47 2026 +0200

    [v3-2-test] Set JWT refresh cookie Secure flag when request is HTTPS 
(#65348) (#65363)
    
    JWTRefreshMiddleware derived the cookie Secure flag from the local
    api.ssl_cert config only. Deployments with TLS terminated at a
    reverse proxy (no local SSL cert on the Airflow process) therefore
    received the JWT refresh cookie without the Secure flag.
    
    Match the pattern already used by every other cookie-setting
    location in the codebase (auth.py, simple/routes/login.py, FAB and
    Keycloak login routes): treat secure as True when either the
    request came in over HTTPS or a local ssl_cert is configured.
    (cherry picked from commit 60db83fa80233c330933bb65123bdce14dfc5c26)
    
    
    Generated-by: Claude Opus 4.6 (1M context) following the guidelines at
    https: 
//github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions
    
    Co-authored-by: Jarek Potiuk <[email protected]>
---
 .../api_fastapi/auth/middlewares/refresh_token.py  |  2 +-
 .../auth/middlewares/test_refresh_token.py         | 45 ++++++++++++++++++++++
 2 files changed, 46 insertions(+), 1 deletion(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/auth/middlewares/refresh_token.py 
b/airflow-core/src/airflow/api_fastapi/auth/middlewares/refresh_token.py
index b8a3a268ba8..179bb1ad07e 100644
--- a/airflow-core/src/airflow/api_fastapi/auth/middlewares/refresh_token.py
+++ b/airflow-core/src/airflow/api_fastapi/auth/middlewares/refresh_token.py
@@ -62,7 +62,7 @@ class JWTRefreshMiddleware(BaseHTTPMiddleware):
 
             if new_token is not None:
                 cookie_path = get_cookie_path()
-                secure = bool(conf.get("api", "ssl_cert", fallback=""))
+                secure = request.base_url.scheme == "https" or 
bool(conf.get("api", "ssl_cert", fallback=""))
                 response.set_cookie(
                     COOKIE_NAME_JWT_TOKEN,
                     new_token,
diff --git 
a/airflow-core/tests/unit/api_fastapi/auth/middlewares/test_refresh_token.py 
b/airflow-core/tests/unit/api_fastapi/auth/middlewares/test_refresh_token.py
index 34b30ba1e7e..2ebfe2cc71b 100644
--- a/airflow-core/tests/unit/api_fastapi/auth/middlewares/test_refresh_token.py
+++ b/airflow-core/tests/unit/api_fastapi/auth/middlewares/test_refresh_token.py
@@ -131,6 +131,51 @@ class TestJWTRefreshMiddleware:
         set_cookie_headers = response.headers.get("set-cookie", "")
         assert f"{COOKIE_NAME_JWT_TOKEN}=new_token" in set_cookie_headers
 
+    @pytest.mark.parametrize(
+        ("scheme", "ssl_cert", "expected_secure"),
+        [
+            pytest.param("https", "", True, id="https-no-local-ssl-cert"),
+            pytest.param("http", "/etc/ssl/cert.pem", True, 
id="http-with-local-ssl-cert"),
+            pytest.param("https", "/etc/ssl/cert.pem", True, 
id="https-with-local-ssl-cert"),
+            pytest.param("http", "", False, id="http-no-local-ssl-cert"),
+        ],
+    )
+    
@patch("airflow.api_fastapi.auth.middlewares.refresh_token.get_auth_manager")
+    
@patch("airflow.api_fastapi.auth.middlewares.refresh_token.resolve_user_from_token")
+    @patch("airflow.api_fastapi.auth.middlewares.refresh_token.conf")
+    @pytest.mark.asyncio
+    async def test_dispatch_cookie_secure_flag(
+        self,
+        mock_conf,
+        mock_resolve_user_from_token,
+        mock_get_auth_manager,
+        middleware,
+        mock_request,
+        mock_user,
+        scheme,
+        ssl_cert,
+        expected_secure,
+    ):
+        """The cookie Secure flag must follow the request scheme as well as 
the local ssl_cert."""
+        refreshed_user = MagicMock(spec=BaseUser)
+        mock_request.cookies = {COOKIE_NAME_JWT_TOKEN: "valid_token"}
+        mock_request.base_url = MagicMock(scheme=scheme)
+        mock_resolve_user_from_token.return_value = mock_user
+        mock_auth_manager = MagicMock()
+        mock_get_auth_manager.return_value = mock_auth_manager
+        mock_auth_manager.refresh_user.return_value = refreshed_user
+        mock_auth_manager.generate_jwt.return_value = "new_token"
+        mock_conf.get.return_value = ssl_cert
+
+        call_next = AsyncMock(return_value=Response())
+        response = await middleware.dispatch(mock_request, call_next)
+
+        set_cookie_headers = response.headers.get("set-cookie", "")
+        if expected_secure:
+            assert "Secure" in set_cookie_headers
+        else:
+            assert "Secure" not in set_cookie_headers
+
     
@patch("airflow.api_fastapi.auth.middlewares.refresh_token.get_cookie_path", 
return_value="/team-a/")
     
@patch("airflow.api_fastapi.auth.middlewares.refresh_token.get_auth_manager")
     
@patch("airflow.api_fastapi.auth.middlewares.refresh_token.resolve_user_from_token")

Reply via email to