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>
92 lines
2.5 KiB
Python
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")
|