strat-gameplay-webapp/backend/app/utils/cookies.py
Cal Corum 9f88317b79 CLAUDE: Fix cookie security and SSR data fetching for iPad/Safari
Resolved WebSocket connection issues and games list loading on iPad:

- cookies.py: Added is_secure_context() to set Secure flag when accessed
  via HTTPS even in development mode (Safari requires this)
- useWebSocket.ts: Changed auto-connect from immediate watcher to
  onMounted hook for safer SSR hydration
- games/index.vue: Replaced onMounted + fetchGames() with useAsyncData
  for SSR data fetching with proper cookie forwarding
- Updated COOKIE_AUTH_IMPLEMENTATION.md with new issues and solutions
- Updated composables/CLAUDE.md with auto-connect pattern documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 21:06:42 -06:00

92 lines
2.5 KiB
Python

"""
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 # 1 hour
REFRESH_TOKEN_MAX_AGE = 60 * 60 * 24 * 7 # 7 days
def is_secure_context() -> bool:
"""
Check if cookies should use Secure flag.
Returns True if:
- APP_ENV is 'production', OR
- FRONTEND_URL starts with 'https://'
This ensures cookies work correctly when accessing via HTTPS
even in development mode.
"""
if getattr(settings, "app_env", "development") == "production":
return True
# Also enable Secure if frontend URL is HTTPS (common in dev with tunnels/proxies)
frontend_url = getattr(settings, "frontend_url", "")
return frontend_url.startswith("https://")
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: HTTPS only in production
- SameSite=Lax: CSRF protection while allowing top-level navigations
- Path: Limits cookie scope
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)
response.set_cookie(
key=ACCESS_TOKEN_COOKIE,
value=access_token,
max_age=ACCESS_TOKEN_MAX_AGE,
httponly=True,
secure=is_secure_context(),
samesite="lax",
path="/", # Root path so cookies are sent with all requests including SSR
)
# 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=is_secure_context(),
samesite="lax",
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")