This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 60db83fa802 Set JWT refresh cookie Secure flag when request is HTTPS
(#65348)
60db83fa802 is described below
commit 60db83fa80233c330933bb65123bdce14dfc5c26
Author: Jarek Potiuk <[email protected]>
AuthorDate: Thu Apr 16 11:59:16 2026 +0200
Set JWT refresh cookie Secure flag when request is HTTPS (#65348)
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.
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
---
.../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")