feat: simplify auth flow - replace JWT with session cookies

Replace complex JWT + refresh token authentication with simple
session-based auth using signed cookies.

**Removed:**
- JWT token service (jwt_service.py)
- Refresh token store (refresh_store.py)
- Refresh token model and database table
- JWKS fetching and OIDC token verification
- python-jose dependency

**Added:**
- Session service (session.py) with HMAC-SHA256 signed cookies
- Auth dependencies module for shared auth logic
- Session-based auth endpoints

**Updated:**
- All API endpoints to use session-based auth
- Config: removed JWT settings, added SESSION_SECRET/SESSION_TTL_HOURS
- Tests: rewritten for session-based flow
- Frontend: no changes needed (already uses cookies)

Quality gates: ruff ✓, mypy ✓, typecheck ✓, lint ✓
This commit is contained in:
Fusion
2026-05-18 22:54:53 +02:00
parent 285d3dace8
commit 2ce7862058
25 changed files with 600 additions and 658 deletions
+11 -24
View File
@@ -1,7 +1,7 @@
from typing import Any
from urllib.parse import urlencode
import httpx
from jose import jwt # type: ignore[import-untyped]
from src.config import Settings
@@ -11,7 +11,6 @@ def build_login_redirect_url(
settings: Settings,
redirect_uri: str,
state: str,
nonce: str,
) -> str:
query = urlencode(
{
@@ -20,7 +19,6 @@ def build_login_redirect_url(
"redirect_uri": redirect_uri,
"scope": "openid profile email",
"state": state,
"nonce": nonce,
}
)
return f"{settings.resolved_authentik_authorize_url}?{query}"
@@ -51,27 +49,16 @@ async def exchange_code_for_tokens(
}
async def fetch_jwks(*, settings: Settings, client: httpx.AsyncClient) -> dict[str, list[dict[str, str]]]:
response = await client.get(settings.resolved_authentik_jwks_url)
response.raise_for_status()
payload = response.json()
return {"keys": payload["keys"]}
def verify_provider_access_token(
async def fetch_user_info(
*,
settings: Settings,
token: str,
jwks: dict[str, list[dict[str, str]]],
) -> dict[str, str | int]:
unverified_header = jwt.get_unverified_header(token)
key_id = unverified_header["kid"]
jwk_key = next(key for key in jwks["keys"] if key.get("kid") == key_id)
claims = jwt.decode(
token,
jwk_key,
algorithms=[jwk_key.get("alg", "HS256")],
audience=settings.authentik_audience,
issuer=settings.resolved_authentik_issuer,
access_token: str,
client: httpx.AsyncClient,
) -> dict[str, Any]:
"""Fetch user info from Authentik userinfo endpoint."""
response = await client.get(
f"{settings.authentik_base_url}/application/o/userinfo/",
headers={"Authorization": f"Bearer {access_token}"},
)
return dict(claims)
response.raise_for_status()
return response.json()