Auth: OIDC Code→Token Exchange Callback + Cookie-basiertes Login
This commit is contained in:
parent
4c8b180383
commit
c3bcf1501d
50
app/main.py
50
app/main.py
@ -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}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user