From 35693a61e3f3e926f8615e90272ed9ba1df6a031 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Fri, 8 May 2026 13:58:12 +0200 Subject: [PATCH] Cross-Site cookies enabled --- modules/auth/jwtService.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/modules/auth/jwtService.py b/modules/auth/jwtService.py index 439d3282..422a4951 100644 --- a/modules/auth/jwtService.py +++ b/modules/auth/jwtService.py @@ -24,6 +24,11 @@ REFRESH_TOKEN_EXPIRE_DAYS = int(APP_CONFIG.get("APP_REFRESH_TOKEN_EXPIRY", "7")) APP_API_URL = APP_CONFIG.get("APP_API_URL", "http://localhost:8000") USE_SECURE_COOKIES = APP_API_URL.startswith("https://") if APP_API_URL else False +# Cross-origin SPA (any host) + API (gateway host): SameSite=None + Secure is required so the +# browser sends cookies on credentialed XHR/fetch. HTTP localhost uses Lax (None without Secure is rejected). +COOKIE_SAMESITE = "none" if USE_SECURE_COOKIES else "lax" +COOKIE_SAMESITE_SET_COOKIE = "None" if USE_SECURE_COOKIES else "Lax" + def createAccessToken(data: dict, expiresDelta: Optional[timedelta] = None) -> Tuple[str, "datetime"]: """Create a JWT access token and return (token, expiresAt).""" @@ -60,7 +65,7 @@ def setAccessTokenCookie(response: Response, token: str, expiresDelta: Optional[ value=token, httponly=True, secure=USE_SECURE_COOKIES, # Only secure in production (HTTPS) - samesite="strict", + samesite=COOKIE_SAMESITE, path="/", max_age=maxAge ) @@ -73,7 +78,7 @@ def setRefreshTokenCookie(response: Response, token: str) -> None: value=token, httponly=True, secure=USE_SECURE_COOKIES, # Only secure in production (HTTPS) - samesite="strict", + samesite=COOKIE_SAMESITE, path="/", max_age=REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60 ) @@ -90,11 +95,17 @@ def clearAccessTokenCookie(response: Response) -> None: # Primary method: Raw Set-Cookie header for guaranteed deletion response.headers.append( "Set-Cookie", - f"auth_token=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly{secure_flag}; SameSite=Strict" + f"auth_token=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly{secure_flag}; SameSite={COOKIE_SAMESITE_SET_COOKIE}" ) - # Fallback: Also use FastAPI's built-in method - response.delete_cookie(key="auth_token", path="/") + # Fallback: Also use FastAPI's built-in method (match SameSite/Secure for invalidation) + response.delete_cookie( + key="auth_token", + path="/", + secure=USE_SECURE_COOKIES, + httponly=True, + samesite=COOKIE_SAMESITE, + ) def clearRefreshTokenCookie(response: Response) -> None: @@ -108,10 +119,16 @@ def clearRefreshTokenCookie(response: Response) -> None: # Primary method: Raw Set-Cookie header for guaranteed deletion response.headers.append( "Set-Cookie", - f"refresh_token=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly{secure_flag}; SameSite=Strict" + f"refresh_token=deleted; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly{secure_flag}; SameSite={COOKIE_SAMESITE_SET_COOKIE}" ) - # Fallback: Also use FastAPI's built-in method - response.delete_cookie(key="refresh_token", path="/") + # Fallback: Also use FastAPI's built-in method (match SameSite/Secure for invalidation) + response.delete_cookie( + key="refresh_token", + path="/", + secure=USE_SECURE_COOKIES, + httponly=True, + samesite=COOKIE_SAMESITE, + )