Auth: OIDC Code→Token Exchange Callback + Cookie-basiertes Login

This commit is contained in:
Dotty Dotter 2026-04-10 21:18:10 +02:00
parent 4c8b180383
commit c3bcf1501d

View File

@ -291,16 +291,58 @@ async def auth_me(user=Depends(get_current_user)):
return {"authenticated": False} return {"authenticated": False}
@app.get("/api/auth/callback")
async def auth_callback(request: Request, code: str = "", state: str = ""):
"""OIDC Authorization Code → Access Token Exchange.
Keycloak redirects hierher nach Login mit ?code=... Parameter.
Wir tauschen den Code gegen ein Access Token und setzen es als Cookie.
"""
if not _is_auth_enabled() or not code:
from fastapi.responses import RedirectResponse
return RedirectResponse("/")
from .auth import _keycloak_issuer
token_url = f"{_keycloak_issuer()}/protocol/openid-connect/token"
# Construct the same redirect_uri used for the auth request
base = str(request.base_url).rstrip("/").replace("http://", "https://")
redirect_uri = f"{base}/api/auth/callback"
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.post(token_url, data={
"grant_type": "authorization_code",
"client_id": settings.keycloak_client_id,
"code": code,
"redirect_uri": redirect_uri,
})
if resp.status_code != 200:
logger.error("Token exchange failed: %s %s", resp.status_code, resp.text[:200])
raise HTTPException(status_code=401, detail="Login fehlgeschlagen")
tokens = resp.json()
access_token = tokens.get("access_token", "")
from fastapi.responses import RedirectResponse
response = RedirectResponse("/")
response.set_cookie(
"access_token", access_token,
httponly=True, secure=True, samesite="lax",
max_age=tokens.get("expires_in", 3600),
)
return response
@app.get("/api/auth/login-url") @app.get("/api/auth/login-url")
async def auth_login_url(request: Request, redirect: str = "/"): async def auth_login_url(request: Request, redirect: str = "/"):
"""Keycloak-Login-URL für den Browser-Redirect.""" """Keycloak-Login-URL für den Browser-Redirect."""
if not _is_auth_enabled(): if not _is_auth_enabled():
return {"enabled": False, "url": ""} return {"enabled": False, "url": ""}
# Construct absolute redirect URI. Traefik terminiert TLS, deshalb # redirect_uri muss auf den Callback-Endpoint zeigen, nicht auf die
# sieht FastAPI http:// als base_url. Erzwinge https:// damit die # Zielseite — der Callback tauscht den Code gegen ein Token.
# redirect_uri zum Keycloak-Client-Config passt.
base = str(request.base_url).rstrip("/").replace("http://", "https://") base = str(request.base_url).rstrip("/").replace("http://", "https://")
url = keycloak_login_url(f"{base}{redirect}") url = keycloak_login_url(f"{base}/api/auth/callback")
return {"enabled": True, "url": url} return {"enabled": True, "url": url}