""" Cookie Utilities for Authentication Handles HttpOnly cookie creation for JWT tokens. Supports both access and refresh tokens with appropriate security settings. Author: Claude (Jarvis) Date: 2025-11-27 """ from fastapi import Response from app.config import get_settings settings = get_settings() # Cookie configuration ACCESS_TOKEN_COOKIE = "pd_access_token" REFRESH_TOKEN_COOKIE = "pd_refresh_token" ACCESS_TOKEN_MAX_AGE = 60 * 60 * 24 # 24 hours REFRESH_TOKEN_MAX_AGE = 60 * 60 * 24 * 7 # 7 days def is_secure_context() -> bool: """ Check if cookies should use Secure flag. Returns True ONLY if APP_ENV is 'production'. In development, we don't set Secure flag even if FRONTEND_URL is HTTPS, because the WebSocket may connect to localhost (HTTP) and secure cookies won't be sent over HTTP connections. For production deployments behind HTTPS reverse proxy, set APP_ENV=production. """ return getattr(settings, "app_env", "development") == "production" def set_auth_cookies( response: Response, access_token: str, refresh_token: str, ) -> None: """ Set both access and refresh token cookies on response. Security settings: - HttpOnly: Prevents XSS access to tokens - Secure: True (required for SameSite=None) - SameSite=None: Required for Safari to send cookies with fetch/XHR requests - Path: Limits cookie scope Note: Safari's ITP treats SameSite=Lax cookies as "not sent" for XHR/fetch requests even on same-origin. SameSite=None with Secure=true fixes this. Args: response: FastAPI Response object access_token: JWT access token refresh_token: JWT refresh token """ # Access token - short-lived, sent to all requests (needed for SSR cookie forwarding) # Using SameSite=None for Safari compatibility (requires Secure=true) response.set_cookie( key=ACCESS_TOKEN_COOKIE, value=access_token, max_age=ACCESS_TOKEN_MAX_AGE, httponly=True, secure=True, # Required for SameSite=None samesite="none", # Safari requires this for fetch() to include cookies path="/", ) # Refresh token - long-lived, restricted to auth endpoints only response.set_cookie( key=REFRESH_TOKEN_COOKIE, value=refresh_token, max_age=REFRESH_TOKEN_MAX_AGE, httponly=True, secure=True, # Required for SameSite=None samesite="none", # Safari requires this for fetch() to include cookies path="/api/auth", ) def clear_auth_cookies(response: Response) -> None: """ Clear all auth cookies (logout). Args: response: FastAPI Response object """ response.delete_cookie(key=ACCESS_TOKEN_COOKIE, path="/") response.delete_cookie(key=REFRESH_TOKEN_COOKIE, path="/api/auth")