feat: Refactor cookie handling to use utility functions for setting access and refresh tokens
This commit is contained in:
@@ -27,6 +27,7 @@ from app.schemas.auth import (
|
|||||||
from app.services.auth import AuthService
|
from app.services.auth import AuthService
|
||||||
from app.services.oauth import OAuthService
|
from app.services.oauth import OAuthService
|
||||||
from app.utils.auth import JWTUtils, TokenUtils
|
from app.utils.auth import JWTUtils, TokenUtils
|
||||||
|
from app.utils.cookies import set_access_token_cookie, set_auth_cookies
|
||||||
|
|
||||||
router = APIRouter(prefix="/auth", tags=["authentication"])
|
router = APIRouter(prefix="/auth", tags=["authentication"])
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -54,26 +55,11 @@ async def register(
|
|||||||
refresh_token = await auth_service.create_and_store_refresh_token(user)
|
refresh_token = await auth_service.create_and_store_refresh_token(user)
|
||||||
|
|
||||||
# Set HTTP-only cookies for both tokens
|
# Set HTTP-only cookies for both tokens
|
||||||
response.set_cookie(
|
set_auth_cookies(
|
||||||
key="access_token",
|
response=response,
|
||||||
value=auth_response.token.access_token,
|
access_token=auth_response.token.access_token,
|
||||||
max_age=auth_response.token.expires_in,
|
refresh_token=refresh_token,
|
||||||
httponly=True,
|
expires_in=auth_response.token.expires_in,
|
||||||
secure=settings.COOKIE_SECURE,
|
|
||||||
samesite=settings.COOKIE_SAMESITE,
|
|
||||||
domain="localhost", # Allow cookie across localhost ports
|
|
||||||
)
|
|
||||||
response.set_cookie(
|
|
||||||
key="refresh_token",
|
|
||||||
value=refresh_token,
|
|
||||||
max_age=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS
|
|
||||||
* 24
|
|
||||||
* 60
|
|
||||||
* 60, # Convert days to seconds
|
|
||||||
httponly=True,
|
|
||||||
secure=settings.COOKIE_SECURE,
|
|
||||||
samesite=settings.COOKIE_SAMESITE,
|
|
||||||
domain="localhost", # Allow cookie across localhost ports
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
@@ -103,26 +89,11 @@ async def login(
|
|||||||
refresh_token = await auth_service.create_and_store_refresh_token(user)
|
refresh_token = await auth_service.create_and_store_refresh_token(user)
|
||||||
|
|
||||||
# Set HTTP-only cookies for both tokens
|
# Set HTTP-only cookies for both tokens
|
||||||
response.set_cookie(
|
set_auth_cookies(
|
||||||
key="access_token",
|
response=response,
|
||||||
value=auth_response.token.access_token,
|
access_token=auth_response.token.access_token,
|
||||||
max_age=auth_response.token.expires_in,
|
refresh_token=refresh_token,
|
||||||
httponly=True,
|
expires_in=auth_response.token.expires_in,
|
||||||
secure=settings.COOKIE_SECURE,
|
|
||||||
samesite=settings.COOKIE_SAMESITE,
|
|
||||||
domain="localhost", # Allow cookie across localhost ports
|
|
||||||
)
|
|
||||||
response.set_cookie(
|
|
||||||
key="refresh_token",
|
|
||||||
value=refresh_token,
|
|
||||||
max_age=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS
|
|
||||||
* 24
|
|
||||||
* 60
|
|
||||||
* 60, # Convert days to seconds
|
|
||||||
httponly=True,
|
|
||||||
secure=settings.COOKIE_SECURE,
|
|
||||||
samesite=settings.COOKIE_SAMESITE,
|
|
||||||
domain="localhost", # Allow cookie across localhost ports
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
@@ -171,14 +142,10 @@ async def refresh_token(
|
|||||||
token_response = await auth_service.refresh_access_token(refresh_token)
|
token_response = await auth_service.refresh_access_token(refresh_token)
|
||||||
|
|
||||||
# Set new access token cookie
|
# Set new access token cookie
|
||||||
response.set_cookie(
|
set_access_token_cookie(
|
||||||
key="access_token",
|
response=response,
|
||||||
value=token_response.access_token,
|
access_token=token_response.access_token,
|
||||||
max_age=token_response.expires_in,
|
expires_in=token_response.expires_in,
|
||||||
httponly=True,
|
|
||||||
secure=settings.COOKIE_SECURE,
|
|
||||||
samesite=settings.COOKIE_SAMESITE,
|
|
||||||
domain="localhost", # Allow cookie across localhost ports
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
@@ -307,24 +274,11 @@ async def oauth_callback(
|
|||||||
# Set HTTP-only cookies for both tokens (not used due to cross-port issues)
|
# Set HTTP-only cookies for both tokens (not used due to cross-port issues)
|
||||||
# These cookies are kept for potential future same-origin scenarios
|
# These cookies are kept for potential future same-origin scenarios
|
||||||
|
|
||||||
response.set_cookie(
|
set_auth_cookies(
|
||||||
key="access_token",
|
response=response,
|
||||||
value=auth_response.token.access_token,
|
access_token=auth_response.token.access_token,
|
||||||
max_age=auth_response.token.expires_in,
|
refresh_token=refresh_token,
|
||||||
httponly=True,
|
expires_in=auth_response.token.expires_in,
|
||||||
secure=settings.COOKIE_SECURE,
|
|
||||||
samesite=settings.COOKIE_SAMESITE,
|
|
||||||
domain="localhost", # Allow cookie across localhost ports
|
|
||||||
path="/", # Ensure cookie is available for all paths
|
|
||||||
)
|
|
||||||
response.set_cookie(
|
|
||||||
key="refresh_token",
|
|
||||||
value=refresh_token,
|
|
||||||
max_age=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60,
|
|
||||||
httponly=True,
|
|
||||||
secure=settings.COOKIE_SECURE,
|
|
||||||
samesite=settings.COOKIE_SAMESITE,
|
|
||||||
domain="localhost", # Allow cookie across localhost ports
|
|
||||||
path="/", # Ensure cookie is available for all paths
|
path="/", # Ensure cookie is available for all paths
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -410,24 +364,11 @@ async def exchange_oauth_token(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Set the proper auth cookies
|
# Set the proper auth cookies
|
||||||
response.set_cookie(
|
set_auth_cookies(
|
||||||
key="access_token",
|
response=response,
|
||||||
value=token_data["access_token"],
|
access_token=token_data["access_token"],
|
||||||
max_age=token_data["expires_in"],
|
refresh_token=token_data["refresh_token"],
|
||||||
httponly=True,
|
expires_in=token_data["expires_in"],
|
||||||
secure=settings.COOKIE_SECURE,
|
|
||||||
samesite=settings.COOKIE_SAMESITE,
|
|
||||||
domain="localhost",
|
|
||||||
path="/",
|
|
||||||
)
|
|
||||||
response.set_cookie(
|
|
||||||
key="refresh_token",
|
|
||||||
value=token_data["refresh_token"],
|
|
||||||
max_age=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60,
|
|
||||||
httponly=True,
|
|
||||||
secure=settings.COOKIE_SECURE,
|
|
||||||
samesite=settings.COOKIE_SAMESITE,
|
|
||||||
domain="localhost",
|
|
||||||
path="/",
|
path="/",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
"""Cookie parsing utilities for WebSocket authentication."""
|
"""Cookie parsing and setting utilities for WebSocket and HTTP authentication."""
|
||||||
|
|
||||||
|
from fastapi import Response
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
|
||||||
def parse_cookies(cookie_header: str) -> dict[str, str]:
|
def parse_cookies(cookie_header: str) -> dict[str, str]:
|
||||||
@@ -20,3 +24,52 @@ def extract_access_token_from_cookies(cookie_header: str) -> str | None:
|
|||||||
"""Extract access token from HTTP cookies."""
|
"""Extract access token from HTTP cookies."""
|
||||||
cookies = parse_cookies(cookie_header)
|
cookies = parse_cookies(cookie_header)
|
||||||
return cookies.get("access_token")
|
return cookies.get("access_token")
|
||||||
|
|
||||||
|
|
||||||
|
def set_access_token_cookie(
|
||||||
|
response: Response,
|
||||||
|
access_token: str,
|
||||||
|
expires_in: int,
|
||||||
|
path: str = "/",
|
||||||
|
) -> None:
|
||||||
|
"""Set access token cookie with consistent configuration."""
|
||||||
|
response.set_cookie(
|
||||||
|
key="access_token",
|
||||||
|
value=access_token,
|
||||||
|
max_age=expires_in,
|
||||||
|
httponly=True,
|
||||||
|
secure=settings.COOKIE_SECURE,
|
||||||
|
samesite=settings.COOKIE_SAMESITE,
|
||||||
|
domain="localhost", # Allow cookie across localhost ports
|
||||||
|
path=path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_refresh_token_cookie(
|
||||||
|
response: Response,
|
||||||
|
refresh_token: str,
|
||||||
|
path: str = "/",
|
||||||
|
) -> None:
|
||||||
|
"""Set refresh token cookie with consistent configuration."""
|
||||||
|
response.set_cookie(
|
||||||
|
key="refresh_token",
|
||||||
|
value=refresh_token,
|
||||||
|
max_age=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60,
|
||||||
|
httponly=True,
|
||||||
|
secure=settings.COOKIE_SECURE,
|
||||||
|
samesite=settings.COOKIE_SAMESITE,
|
||||||
|
domain="localhost", # Allow cookie across localhost ports
|
||||||
|
path=path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_auth_cookies(
|
||||||
|
response: Response,
|
||||||
|
access_token: str,
|
||||||
|
refresh_token: str,
|
||||||
|
expires_in: int,
|
||||||
|
path: str = "/",
|
||||||
|
) -> None:
|
||||||
|
"""Set both access and refresh token cookies with consistent configuration."""
|
||||||
|
set_access_token_cookie(response, access_token, expires_in, path)
|
||||||
|
set_refresh_token_cookie(response, refresh_token, path)
|
||||||
|
|||||||
Reference in New Issue
Block a user