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]