Enabled cross-subdomain console sessions by making the cookie domain configurable and aligning the frontend so it reads the shared CSRF cookie. (#27190)

This commit is contained in:
Eric Guo
2025-10-28 10:04:24 +08:00
committed by GitHub
parent 543c5236e7
commit ff32dff163
10 changed files with 94 additions and 13 deletions

View File

@@ -30,8 +30,22 @@ def is_secure() -> bool:
return dify_config.CONSOLE_WEB_URL.startswith("https") and dify_config.CONSOLE_API_URL.startswith("https")
def _cookie_domain() -> str | None:
"""
Returns the normalized cookie domain.
Leading dots are stripped from the configured domain. Historically, a leading dot
indicated that a cookie should be sent to all subdomains, but modern browsers treat
'example.com' and '.example.com' identically. This normalization ensures consistent
behavior and avoids confusion.
"""
domain = dify_config.COOKIE_DOMAIN.strip()
domain = domain.removeprefix(".")
return domain or None
def _real_cookie_name(cookie_name: str) -> str:
if is_secure():
if is_secure() and _cookie_domain() is None:
return "__Host-" + cookie_name
else:
return cookie_name
@@ -91,6 +105,7 @@ def set_access_token_to_cookie(request: Request, response: Response, token: str,
_real_cookie_name(COOKIE_NAME_ACCESS_TOKEN),
value=token,
httponly=True,
domain=_cookie_domain(),
secure=is_secure(),
samesite=samesite,
max_age=int(dify_config.ACCESS_TOKEN_EXPIRE_MINUTES * 60),
@@ -103,6 +118,7 @@ def set_refresh_token_to_cookie(request: Request, response: Response, token: str
_real_cookie_name(COOKIE_NAME_REFRESH_TOKEN),
value=token,
httponly=True,
domain=_cookie_domain(),
secure=is_secure(),
samesite="Lax",
max_age=int(60 * 60 * 24 * dify_config.REFRESH_TOKEN_EXPIRE_DAYS),
@@ -115,6 +131,7 @@ def set_csrf_token_to_cookie(request: Request, response: Response, token: str):
_real_cookie_name(COOKIE_NAME_CSRF_TOKEN),
value=token,
httponly=False,
domain=_cookie_domain(),
secure=is_secure(),
samesite="Lax",
max_age=int(60 * dify_config.ACCESS_TOKEN_EXPIRE_MINUTES),
@@ -133,6 +150,7 @@ def _clear_cookie(
"",
expires=0,
path="/",
domain=_cookie_domain(),
secure=is_secure(),
httponly=http_only,
samesite=samesite,
@@ -155,6 +173,19 @@ def clear_csrf_token_from_cookie(response: Response):
_clear_cookie(response, COOKIE_NAME_CSRF_TOKEN, http_only=False)
def build_force_logout_cookie_headers() -> list[str]:
"""
Generate Set-Cookie header values that clear all auth-related cookies.
This mirrors the behavior of the standard cookie clearing helpers while
allowing callers that do not have a Response instance to reuse the logic.
"""
response = Response()
clear_access_token_from_cookie(response)
clear_csrf_token_from_cookie(response)
clear_refresh_token_from_cookie(response)
return response.headers.getlist("Set-Cookie")
def check_csrf_token(request: Request, user_id: str):
# some apis are sent by beacon, so we need to bypass csrf token check
# since these APIs are post, they are already protected by SameSite: Lax, so csrf is not required.