Harden session cookie security with configurable cookie flags

This commit is contained in:
2026-04-09 21:43:32 +02:00
parent 208f98dc97
commit 4043cdfa3c
4 changed files with 41 additions and 3 deletions

View File

@@ -4,6 +4,8 @@ Follows pydantic-settings patterns: all values are prefixed with BANGUI_
and validated at startup via the Settings singleton.
"""
from typing import Literal
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -47,6 +49,25 @@ class Settings(BaseSettings):
default="UTC",
description="IANA timezone name used when displaying timestamps in the UI.",
)
session_cookie_httponly: bool = Field(
default=True,
description=(
"Mark the session cookie as HttpOnly so browser scripts cannot access it."
),
)
session_cookie_samesite: Literal["lax", "strict", "none"] = Field(
default="lax",
description=(
"SameSite policy for the session cookie. "
"Use 'lax', 'strict', or 'none' depending on deployment requirements."
),
)
session_cookie_secure: bool = Field(
default=False,
description=(
"Set the session cookie Secure flag when the backend is served over HTTPS."
),
)
cors_allowed_origins: str | list[str] = Field(
default_factory=list,
description=(

View File

@@ -70,9 +70,9 @@ async def login(
response.set_cookie(
key=_COOKIE_NAME,
value=signed_token,
httponly=True,
samesite="lax",
secure=False, # Set to True in production behind HTTPS
httponly=settings.session_cookie_httponly,
samesite=settings.session_cookie_samesite,
secure=settings.session_cookie_secure,
max_age=settings.session_duration_minutes * 60,
)
return LoginResponse(token=signed_token, expires_at=session.expires_at)

View File

@@ -66,6 +66,22 @@ class TestLogin:
assert response.status_code == 200
assert "bangui_session" in response.cookies
assert "." in response.cookies["bangui_session"]
set_cookie = response.headers.get("set-cookie", "")
assert "HttpOnly" in set_cookie
assert "SameSite=lax" in set_cookie
async def test_login_sets_secure_cookie_when_enabled(
self, client: AsyncClient
) -> None:
"""Login sets the Secure flag when session cookies are configured for HTTPS."""
client._transport.app.state.settings.session_cookie_secure = True
await _do_setup(client)
response = await client.post(
"/api/auth/login", json={"password": "mysecretpass1"}
)
assert response.status_code == 200
set_cookie = response.headers.get("set-cookie", "")
assert "Secure" in set_cookie
async def test_login_fails_with_wrong_password(
self, client: AsyncClient