fix: Cookies now properly cleared on logout

This commit is contained in:
Ida Dittrich 2025-10-12 16:13:27 +02:00
parent dedee0ecda
commit 4e06f7e661
2 changed files with 48 additions and 8 deletions

View file

@ -14,7 +14,7 @@ from pydantic import BaseModel
# Import auth modules # Import auth modules
from modules.security.auth import getCurrentUser, limiter, SECRET_KEY, ALGORITHM from modules.security.auth import getCurrentUser, limiter, SECRET_KEY, ALGORITHM
from modules.security.jwtService import createAccessToken, createRefreshToken, setAccessTokenCookie, setRefreshTokenCookie from modules.security.jwtService import createAccessToken, createRefreshToken, setAccessTokenCookie, setRefreshTokenCookie, clearAccessTokenCookie, clearRefreshTokenCookie
from modules.interfaces.interfaceDbAppObjects import getInterface, getRootInterface from modules.interfaces.interfaceDbAppObjects import getInterface, getRootInterface
from modules.datamodels.datamodelUam import User, UserInDB, AuthAuthority, UserPrivilege from modules.datamodels.datamodelUam import User, UserInDB, AuthAuthority, UserPrivilege
from modules.datamodels.datamodelSecurity import Token from modules.datamodels.datamodelSecurity import Token
@ -365,15 +365,18 @@ async def logout(request: Request, response: Response, currentUser: User = Depen
# Don't fail if audit logging fails # Don't fail if audit logging fails
pass pass
# Clear httpOnly cookies # Create the JSON response first
response.delete_cookie(key="auth_token", httponly=True, samesite="strict") json_response = JSONResponse({
response.delete_cookie(key="refresh_token", httponly=True, samesite="strict")
return JSONResponse({
"message": "Successfully logged out - cookies cleared", "message": "Successfully logged out - cookies cleared",
"revokedTokens": revoked "revokedTokens": revoked
}) })
# Clear httpOnly cookies on the response we're actually returning
clearAccessTokenCookie(json_response)
clearRefreshTokenCookie(json_response)
return json_response
except Exception as e: except Exception as e:
logger.error(f"Error during logout: {str(e)}") logger.error(f"Error during logout: {str(e)}")
raise HTTPException( raise HTTPException(

View file

@ -17,6 +17,11 @@ ALGORITHM = APP_CONFIG.get("Auth_ALGORITHM")
ACCESS_TOKEN_EXPIRE_MINUTES = int(APP_CONFIG.get("APP_TOKEN_EXPIRY")) ACCESS_TOKEN_EXPIRE_MINUTES = int(APP_CONFIG.get("APP_TOKEN_EXPIRY"))
REFRESH_TOKEN_EXPIRE_DAYS = int(APP_CONFIG.get("APP_REFRESH_TOKEN_EXPIRY", "7")) REFRESH_TOKEN_EXPIRE_DAYS = int(APP_CONFIG.get("APP_REFRESH_TOKEN_EXPIRY", "7"))
# Cookie security settings - use secure cookies only in production (HTTPS)
# In development (HTTP), secure=True would prevent cookies from being set/cleared properly
ENV_TYPE = APP_CONFIG.get("APP_ENV_TYPE", "dev")
USE_SECURE_COOKIES = ENV_TYPE in ["prod", "production"]
def createAccessToken(data: dict, expiresDelta: Optional[timedelta] = None) -> Tuple[str, "datetime"]: def createAccessToken(data: dict, expiresDelta: Optional[timedelta] = None) -> Tuple[str, "datetime"]:
"""Create a JWT access token and return (token, expiresAt).""" """Create a JWT access token and return (token, expiresAt)."""
@ -52,8 +57,9 @@ def setAccessTokenCookie(response: Response, token: str, expiresDelta: Optional[
key="auth_token", key="auth_token",
value=token, value=token,
httponly=True, httponly=True,
secure=True, secure=USE_SECURE_COOKIES, # Only secure in production (HTTPS)
samesite="strict", samesite="strict",
path="/",
max_age=maxAge max_age=maxAge
) )
@ -64,9 +70,40 @@ def setRefreshTokenCookie(response: Response, token: str) -> None:
key="refresh_token", key="refresh_token",
value=token, value=token,
httponly=True, httponly=True,
secure=True, secure=USE_SECURE_COOKIES, # Only secure in production (HTTPS)
samesite="strict", samesite="strict",
path="/",
max_age=REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60 max_age=REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60
) )
def clearAccessTokenCookie(response: Response) -> None:
"""
Clear access token cookie by setting it to expire immediately.
Uses both raw header manipulation and FastAPI's delete_cookie for maximum browser compatibility.
"""
# 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; SameSite=Strict"
)
# Fallback: Also use FastAPI's built-in method
response.delete_cookie(key="auth_token", path="/")
def clearRefreshTokenCookie(response: Response) -> None:
"""
Clear refresh token cookie by setting it to expire immediately.
Uses both raw header manipulation and FastAPI's delete_cookie for maximum browser compatibility.
"""
# 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; SameSite=Strict"
)
# Fallback: Also use FastAPI's built-in method
response.delete_cookie(key="refresh_token", path="/")