stegololz commented on code in PR #73365:
URL: https://github.com/apache/airflow/pull/73365#discussion_r4062027590


##########
airflow-core/docs/core-concepts/auth-manager/index.rst:
##########
@@ -194,22 +194,127 @@ available at ``POST /auth/token``.
 Please double check the auth manager documentation to find the accurate token 
generation endpoint.
 
 The auth manager is also responsible for passing the JWT token to the Airflow 
UI. The protocol to exchange the JWT
-token between the auth manager and Airflow UI is using cookies. The auth 
manager needs to save the JWT token in a
-cookie named ``_token`` before redirecting to the Airflow UI. The Airflow UI 
will then read the cookie, save it, and delete it.
+token between the auth manager and Airflow UI uses a cookie named ``_token``. 
The auth manager must attach this
+cookie to the final response that redirects the authenticated user to the UI. 
The browser then sends the
+``httponly`` cookie with subsequent requests; the UI does not manage the token.
 
-.. code-block:: python
+.. note::
+  Ensure that the cookie parameter ``httponly`` is set to ``True``. The UI 
does not manage the token.
 
-    from airflow.api_fastapi.app import get_cookie_path
-    from airflow.api_fastapi.auth.managers.base_auth_manager import 
COOKIE_NAME_JWT_TOKEN
+Redirect-based UI login flows
+'''''''''''''''''''''''''''''
+
+OAuth, OIDC, SAML, and similar login protocols leave Airflow while the 
identity provider authenticates the user.
+For these flows, complete authentication and set the Airflow JWT cookie before 
returning to the UI:
+
+#. When an unauthenticated UI request receives a ``401``, the UI navigates to 
``/api/v2/auth/login`` and sends its
+   original destination in ``next``.
+#. Airflow validates ``next`` and forwards it to the auth manager's mounted 
login endpoint.
+#. The login endpoint validates the return URL again and preserves it across 
the external redirects in
+   integrity-protected state, together with a unique nonce for this login 
attempt.
+#. The callback verifies and consumes the CSRF/state value, completes the 
provider exchange, verifies the provider
+   credentials, constructs the Airflow user, and calls 
``get_auth_manager().generate_jwt(user)``.
+#. Only after those operations succeed does the callback create the final 
``RedirectResponse`` and attach the
+   ``_token`` cookie to that same response.
+#. The callback returns a ``303`` redirect to the validated return URL, or to 
the configured ``[api] base_url`` when
+   there was no original destination.
+
+The following compact example shows the two auth-manager handlers. The 
``validate_airflow_return_url``,
+``store_login_nonce``, ``sign_login_state``, 
``verify_and_consume_login_state``,
+``build_provider_authorization_url``, and ``exchange_code_and_build_user`` 
helpers are placeholders that the auth
+manager must implement. Airflow does not provide provider token exchange or 
state signing.
 
-    response = RedirectResponse(url="/")
+.. code-block:: python
 
-    secure = request.base_url.scheme == "https" or bool(conf.get("api", 
"ssl_cert", fallback=""))
-    response.set_cookie(COOKIE_NAME_JWT_TOKEN, token, path=get_cookie_path(), 
secure=secure, httponly=True)
-    return response
+    import secrets
+    from urllib.parse import urlsplit, urlunsplit
 
-.. note::
-  Ensure that the cookie parameter ``httponly`` is set to ``True``. The UI 
does not manage the token.
+    from fastapi import APIRouter, HTTPException, Request, status
+    from fastapi.responses import RedirectResponse
+
+    from airflow.api_fastapi.app import (
+        AUTH_MANAGER_FASTAPI_APP_PREFIX,
+        get_auth_manager,
+        get_cookie_path,
+    )
+    from airflow.api_fastapi.auth.managers.base_auth_manager import 
COOKIE_NAME_JWT_TOKEN
+    from airflow.configuration import conf
+
+    router = APIRouter()
+    airflow_base_url = conf.get("api", "base_url", fallback="/")

Review Comment:
   `[api] base_url` has no default. With `fallback="/"`, `urlsplit` returns an 
empty scheme and netloc, so `callback_url` below becomes the relative path 
`/auth/callback`, which identity providers reject as a `redirect_uri`. The 
Keycloak provider handles this in `_login_callback_url` by preferring 
`base_url` and falling back to `request.url_for(...)`, using `urljoin` instead 
of `urlsplit`/`urlunsplit`. I would do the same here, or state that this 
pattern requires an absolute `base_url`.
   
   ---
   Drafted-by: Claude Code (Fable 5.1); reviewed by @stegololz before posting
   



##########
airflow-core/docs/core-concepts/auth-manager/index.rst:
##########
@@ -194,22 +194,127 @@ available at ``POST /auth/token``.
 Please double check the auth manager documentation to find the accurate token 
generation endpoint.
 
 The auth manager is also responsible for passing the JWT token to the Airflow 
UI. The protocol to exchange the JWT
-token between the auth manager and Airflow UI is using cookies. The auth 
manager needs to save the JWT token in a
-cookie named ``_token`` before redirecting to the Airflow UI. The Airflow UI 
will then read the cookie, save it, and delete it.
+token between the auth manager and Airflow UI uses a cookie named ``_token``. 
The auth manager must attach this
+cookie to the final response that redirects the authenticated user to the UI. 
The browser then sends the
+``httponly`` cookie with subsequent requests; the UI does not manage the token.
 
-.. code-block:: python
+.. note::
+  Ensure that the cookie parameter ``httponly`` is set to ``True``. The UI 
does not manage the token.
 
-    from airflow.api_fastapi.app import get_cookie_path
-    from airflow.api_fastapi.auth.managers.base_auth_manager import 
COOKIE_NAME_JWT_TOKEN
+Redirect-based UI login flows
+'''''''''''''''''''''''''''''
+
+OAuth, OIDC, SAML, and similar login protocols leave Airflow while the 
identity provider authenticates the user.
+For these flows, complete authentication and set the Airflow JWT cookie before 
returning to the UI:
+
+#. When an unauthenticated UI request receives a ``401``, the UI navigates to 
``/api/v2/auth/login`` and sends its
+   original destination in ``next``.
+#. Airflow validates ``next`` and forwards it to the auth manager's mounted 
login endpoint.
+#. The login endpoint validates the return URL again and preserves it across 
the external redirects in
+   integrity-protected state, together with a unique nonce for this login 
attempt.
+#. The callback verifies and consumes the CSRF/state value, completes the 
provider exchange, verifies the provider
+   credentials, constructs the Airflow user, and calls 
``get_auth_manager().generate_jwt(user)``.
+#. Only after those operations succeed does the callback create the final 
``RedirectResponse`` and attach the
+   ``_token`` cookie to that same response.
+#. The callback returns a ``303`` redirect to the validated return URL, or to 
the configured ``[api] base_url`` when
+   there was no original destination.
+
+The following compact example shows the two auth-manager handlers. The 
``validate_airflow_return_url``,
+``store_login_nonce``, ``sign_login_state``, 
``verify_and_consume_login_state``,
+``build_provider_authorization_url``, and ``exchange_code_and_build_user`` 
helpers are placeholders that the auth
+manager must implement. Airflow does not provide provider token exchange or 
state signing.
 
-    response = RedirectResponse(url="/")
+.. code-block:: python
 
-    secure = request.base_url.scheme == "https" or bool(conf.get("api", 
"ssl_cert", fallback=""))
-    response.set_cookie(COOKIE_NAME_JWT_TOKEN, token, path=get_cookie_path(), 
secure=secure, httponly=True)
-    return response
+    import secrets
+    from urllib.parse import urlsplit, urlunsplit
 
-.. note::
-  Ensure that the cookie parameter ``httponly`` is set to ``True``. The UI 
does not manage the token.
+    from fastapi import APIRouter, HTTPException, Request, status
+    from fastapi.responses import RedirectResponse
+
+    from airflow.api_fastapi.app import (
+        AUTH_MANAGER_FASTAPI_APP_PREFIX,
+        get_auth_manager,
+        get_cookie_path,
+    )
+    from airflow.api_fastapi.auth.managers.base_auth_manager import 
COOKIE_NAME_JWT_TOKEN
+    from airflow.configuration import conf
+
+    router = APIRouter()
+    airflow_base_url = conf.get("api", "base_url", fallback="/")
+    base_url_parts = urlsplit(airflow_base_url)
+    callback_url = urlunsplit(
+        (
+            base_url_parts.scheme,
+            base_url_parts.netloc,
+            f"{AUTH_MANAGER_FASTAPI_APP_PREFIX.rstrip('/')}/callback",
+            "",
+            "",
+        )
+    )
+
+
+    @router.get("/login")
+    def login(next: str | None = None) -> RedirectResponse:
+        validated_return_url = validate_airflow_return_url(next, 
base_url=airflow_base_url)

Review Comment:
   Core already ships `is_safe_url` in `airflow.api_fastapi.core_api.security`, 
used by the `/auth/login` route and the simple auth manager for exactly this 
check. Worth pointing readers at it instead of a placeholder.
   



##########
airflow-core/docs/core-concepts/auth-manager/index.rst:
##########
@@ -194,22 +194,127 @@ available at ``POST /auth/token``.
 Please double check the auth manager documentation to find the accurate token 
generation endpoint.
 
 The auth manager is also responsible for passing the JWT token to the Airflow 
UI. The protocol to exchange the JWT
-token between the auth manager and Airflow UI is using cookies. The auth 
manager needs to save the JWT token in a
-cookie named ``_token`` before redirecting to the Airflow UI. The Airflow UI 
will then read the cookie, save it, and delete it.
+token between the auth manager and Airflow UI uses a cookie named ``_token``. 
The auth manager must attach this
+cookie to the final response that redirects the authenticated user to the UI. 
The browser then sends the
+``httponly`` cookie with subsequent requests; the UI does not manage the token.
 
-.. code-block:: python
+.. note::
+  Ensure that the cookie parameter ``httponly`` is set to ``True``. The UI 
does not manage the token.

Review Comment:
   nit: this repeats "The UI does not manage the token" from the paragraph 
right above.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to