fix: set cookie domain for cross-subdomain authentication

In production, the session cookie needs to be shared across
subdomains (e.g., api.example.com and app.example.com).

- Add cookie_domain property to config (extracts parent domain)
- Set SameSite=None for cross-origin requests in production
- Update auth callback and logout to use cookie domain
- This fixes the login loop where session cookie wasn't sent
This commit is contained in:
Fusion
2026-05-18 23:33:59 +02:00
parent c067c03662
commit 58bf30ed15
3 changed files with 33 additions and 6 deletions
+14 -4
View File
@@ -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"}
+2 -1
View File
@@ -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,
}
+17 -1
View File
@@ -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