fix(security): set an explicit session cookie lifetime
The session cookie relied on Starlette's default max_age (two weeks), which is easy to miss and longer than an OP session should live. Add a session_max_age setting (default 8 hours) and pass it to SessionMiddleware. Refs: porchlight-1lg Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cf2754f302
commit
cba63280fb
3 changed files with 11 additions and 0 deletions
|
|
@ -143,6 +143,7 @@ def create_app(settings: Settings | None = None) -> FastAPI:
|
||||||
secret_key=session_secret,
|
secret_key=session_secret,
|
||||||
same_site="lax",
|
same_site="lax",
|
||||||
https_only=settings.session_https_only,
|
https_only=settings.session_https_only,
|
||||||
|
max_age=settings.session_max_age,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Rate limiting
|
# Rate limiting
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ class Settings(BaseSettings):
|
||||||
# Session
|
# Session
|
||||||
session_secret: str | None = None # If None, a random secret is generated per process
|
session_secret: str | None = None # If None, a random secret is generated per process
|
||||||
session_https_only: bool = True
|
session_https_only: bool = True
|
||||||
|
session_max_age: int = 28800 # Cookie lifetime in seconds (default 8 hours)
|
||||||
|
|
||||||
# WebAuthn user verification requirement: "preferred" (default), "required",
|
# WebAuthn user verification requirement: "preferred" (default), "required",
|
||||||
# or "discouraged". Identity providers may want "required".
|
# or "discouraged". Identity providers may want "required".
|
||||||
|
|
|
||||||
|
|
@ -75,3 +75,12 @@ def test_create_app_allows_missing_secret_on_localhost() -> None:
|
||||||
def test_create_app_allows_missing_secret_in_debug() -> None:
|
def test_create_app_allows_missing_secret_in_debug() -> None:
|
||||||
settings = Settings(issuer="https://op.example.com", sqlite_path=":memory:", debug=True)
|
settings = Settings(issuer="https://op.example.com", sqlite_path=":memory:", debug=True)
|
||||||
assert create_app(settings) is not None
|
assert create_app(settings) is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_session_cookie_has_explicit_max_age(client: AsyncClient) -> None:
|
||||||
|
# Visiting /login establishes a session (CSRF token), setting the cookie.
|
||||||
|
res = await client.get("/login")
|
||||||
|
set_cookies = res.headers.get_list("set-cookie")
|
||||||
|
session_cookies = [c for c in set_cookies if c.startswith("session=")]
|
||||||
|
assert session_cookies, "no session cookie set"
|
||||||
|
assert "Max-Age=28800" in session_cookies[0]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue