Vamsi-klu commented on code in PR #73365:
URL: https://github.com/apache/airflow/pull/73365#discussion_r4062552334


##########
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:
   You are right that `[api] base_url` has no default. With `fallback="/"` and 
urlsplit, the sample handed providers a relative `/auth/callback`, which they 
reject. I copied the Keycloak helper. Prefer the configured base URL with 
urljoin, and fall back to `request.url_for("callback")` when it is unset.
   
   ---
   Drafted-by: Cursor Grok 4.6; reviewed by @Vamsi-klu 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:
   I dropped the `validate_airflow_return_url` placeholder and pointed the 
example at `is_safe_url` from `airflow.api_fastapi.core_api.security`. That is 
the same check the core `/auth/login` route and the simple auth manager already 
run.
   
   ---
   Drafted-by: Cursor Grok 4.6; reviewed by @Vamsi-klu 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.

Review Comment:
   The repeated "UI does not manage the token" line in the note is gone.
   
   ---
   Drafted-by: Cursor Grok 4.6; reviewed by @Vamsi-klu before posting



-- 
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