This is an automated email from the ASF dual-hosted git repository.
vincbeck 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 a6b9b08d6cd Set `secure` flag on keycloak login cookies behind a tls
proxy (#69594)
a6b9b08d6cd is described below
commit a6b9b08d6cdfb03097395d45e7d23e4e359e0d5f
Author: Samina <[email protected]>
AuthorDate: Wed Jul 8 20:47:40 2026 +0530
Set `secure` flag on keycloak login cookies behind a tls proxy (#69594)
The keycloak auth manager sets the session JWT, the OIDC id token, and the
OAuth state cookie during login, and it decides the cookie Secure flag from
secure = bool(conf.get("api", "ssl_cert")). That drops the
request.base_url.scheme == "https" term the rest of the codebase uses for the
same decision, including logout_callback a few lines down, the core auth
manager login/logout, and the refresh-token middleware. When the api-server
sits behind a proxy that terminates TLS, ssl_cert is [...]
---
.../keycloak/auth_manager/routes/login.py | 4 +-
.../keycloak/auth_manager/routes/test_login.py | 43 ++++++++++++++++++++++
2 files changed, 45 insertions(+), 2 deletions(-)
diff --git
a/providers/keycloak/src/airflow/providers/keycloak/auth_manager/routes/login.py
b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/routes/login.py
index 33ae5ca781a..9f2c80fe46a 100644
---
a/providers/keycloak/src/airflow/providers/keycloak/auth_manager/routes/login.py
+++
b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/routes/login.py
@@ -65,7 +65,7 @@ def login(request: Request) -> RedirectResponse:
state = secrets.token_urlsafe(32)
auth_url = client.auth_url(redirect_uri=str(redirect_uri), scope="openid",
state=state)
response = RedirectResponse(auth_url)
- secure = bool(conf.get("api", "ssl_cert", fallback=""))
+ secure = request.base_url.scheme == "https" or bool(conf.get("api",
"ssl_cert", fallback=""))
cookie_path = get_cookie_path()
response.set_cookie(
COOKIE_NAME_OAUTH_STATE, state, max_age=300, path=cookie_path,
httponly=True, secure=secure
@@ -105,7 +105,7 @@ def login_callback(request: Request):
token = get_auth_manager().generate_jwt(user)
response = RedirectResponse(url=conf.get("api", "base_url", fallback="/"),
status_code=303)
- secure = bool(conf.get("api", "ssl_cert", fallback=""))
+ secure = request.base_url.scheme == "https" or bool(conf.get("api",
"ssl_cert", fallback=""))
# In Airflow 3.1.1 authentication changes, front-end no longer handle the
token
# See https://github.com/apache/airflow/pull/55506
cookie_path = get_cookie_path()
diff --git
a/providers/keycloak/tests/unit/keycloak/auth_manager/routes/test_login.py
b/providers/keycloak/tests/unit/keycloak/auth_manager/routes/test_login.py
index c4529150a3b..4f20715cc76 100644
--- a/providers/keycloak/tests/unit/keycloak/auth_manager/routes/test_login.py
+++ b/providers/keycloak/tests/unit/keycloak/auth_manager/routes/test_login.py
@@ -78,6 +78,49 @@ class TestLoginRouter:
assert response.cookies["_token"] == token
assert response.cookies["_id_token"] == "id_token"
+
@patch("airflow.providers.keycloak.auth_manager.routes.login.KeycloakAuthManager.get_keycloak_client")
+ def test_login_sets_secure_state_cookie_behind_tls_proxy(self,
mock_get_keycloak_client, client):
+ mock_keycloak_client = Mock()
+ mock_keycloak_client.auth_url.return_value = "redirect_url"
+ mock_get_keycloak_client.return_value = mock_keycloak_client
+ response = client.get(
+ "https://testserver" + AUTH_MANAGER_FASTAPI_APP_PREFIX + "/login",
+ follow_redirects=False,
+ )
+ set_cookies = response.headers.get_list("set-cookie")
+ assert any("_oauth_state" in c and "Secure" in c for c in set_cookies)
+
+
@patch("airflow.providers.keycloak.auth_manager.routes.login.get_auth_manager")
+
@patch("airflow.providers.keycloak.auth_manager.routes.login.KeycloakAuthManager.get_keycloak_client")
+ def test_login_callback_sets_secure_cookies_behind_tls_proxy(
+ self, mock_get_keycloak_client, mock_get_auth_manager, client
+ ):
+ state = "state"
+ mock_keycloak_client = Mock()
+ mock_keycloak_client.token.return_value = {
+ "access_token": "access_token",
+ "refresh_token": "refresh_token",
+ "id_token": "id_token",
+ }
+ mock_keycloak_client.userinfo.return_value = {
+ "sub": "sub",
+ "preferred_username": "preferred_username",
+ }
+ mock_get_keycloak_client.return_value = mock_keycloak_client
+ mock_auth_manager = Mock()
+ mock_get_auth_manager.return_value = mock_auth_manager
+ mock_auth_manager.generate_jwt.return_value = "token"
+ response = client.get(
+ "https://testserver"
+ + AUTH_MANAGER_FASTAPI_APP_PREFIX
+ + f"/login_callback?code=code&state={state}",
+ follow_redirects=False,
+ cookies={"_oauth_state": state},
+ )
+ set_cookies = response.headers.get_list("set-cookie")
+ assert any("_token=" in c and "Secure" in c for c in set_cookies)
+ assert any("_id_token=" in c and "Secure" in c for c in set_cookies)
+
def test_login_callback_without_code(self, client):
response = client.get(AUTH_MANAGER_FASTAPI_APP_PREFIX +
"/login_callback")
assert response.status_code == 400