diff --git a/apps/api/src/api/auth.py b/apps/api/src/api/auth.py index 9b4dc1d..259b277 100644 --- a/apps/api/src/api/auth.py +++ b/apps/api/src/api/auth.py @@ -114,9 +114,18 @@ async def callback( cookie_options = build_cookie_options(settings) cookie_samesite = cast(Literal["lax", "strict", "none"], cookie_options["samesite"]) cookie_secure = bool(cookie_options["secure"]) - response.set_cookie("session", session_cookie, httponly=True, samesite=cookie_samesite, secure=cookie_secure) - response.delete_cookie("auth_state", samesite="lax") - response.delete_cookie("auth_next", samesite="lax") + cookie_domain = str(cookie_options["domain"]) if cookie_options.get("domain") else None + + response.set_cookie( + "session", + session_cookie, + httponly=True, + samesite=cookie_samesite, + secure=cookie_secure, + domain=cookie_domain, + ) + response.delete_cookie("auth_state", samesite="lax", domain=cookie_domain) + response.delete_cookie("auth_next", samesite="lax", domain=cookie_domain) logger.info("Auth callback complete for user id=%s, redirecting to %s", user.id, auth_next) @@ -131,8 +140,9 @@ async def logout(response: Response) -> dict[str, str]: cookie_options = build_cookie_options(settings) cookie_samesite = cast(Literal["lax", "strict", "none"], cookie_options["samesite"]) cookie_secure = bool(cookie_options["secure"]) + cookie_domain = str(cookie_options["domain"]) if cookie_options.get("domain") else None - response.delete_cookie("session", samesite=cookie_samesite, secure=cookie_secure) + response.delete_cookie("session", samesite=cookie_samesite, secure=cookie_secure, domain=cookie_domain) return {"status": "ok"} diff --git a/apps/api/src/auth/cookies.py b/apps/api/src/auth/cookies.py index 0a72f98..c7b3e51 100644 --- a/apps/api/src/auth/cookies.py +++ b/apps/api/src/auth/cookies.py @@ -1,9 +1,10 @@ from src.config import Settings -def build_cookie_options(settings: Settings) -> dict[str, str | bool]: +def build_cookie_options(settings: Settings) -> dict[str, str | bool | None]: return { "httponly": True, "secure": settings.cookie_secure, "samesite": settings.cookie_samesite, + "domain": settings.cookie_domain, } diff --git a/apps/api/src/config.py b/apps/api/src/config.py index e2ebb3e..0b91a50 100644 --- a/apps/api/src/config.py +++ b/apps/api/src/config.py @@ -117,6 +117,22 @@ class Settings(BaseSettings): @property def cookie_samesite(self) -> str: if self.app_env == "production": - return "strict" + return "none" return "lax" + + @property + def cookie_domain(self) -> str | None: + """Return the parent domain for cross-subdomain cookies. + + E.g., api.example.com and app.example.com both share .example.com + """ + if self.app_env != "production": + return None + + # Extract parent domain from api_domain + # e.g., "api.headquarter.commumedia.org" -> ".headquarter.commumedia.org" + parts = self.api_domain.split(".") + if len(parts) >= 3: + return "." + ".".join(parts[1:]) + return None