implement automatic login refresh using OIDC refresh tokens

This commit is contained in:
lilly 2026-05-04 11:01:04 +02:00
commit 94fac19546
Signed by: lilly
SSH key fingerprint: SHA256:y9T5GFw2A20WVklhetIxG1+kcg/Ce0shnQmbu1LQ37g
3 changed files with 65 additions and 26 deletions

View file

@ -1,8 +1,8 @@
from typing import Optional
import logging
import secrets
import math
from datetime import datetime, UTC, timedelta
import sys
from datetime import datetime, UTC
from fastapi import FastAPI, Request, Response
from fastapi.responses import RedirectResponse
from contextlib import asynccontextmanager
@ -18,6 +18,12 @@ logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
root_logger = logging.getLogger("")
root_logger.setLevel(logging.INFO)
root_logger.addHandler(logging.StreamHandler(sys.stderr))
app_logger = logging.getLogger("dooris_api")
app_logger.setLevel(logging.DEBUG)
app.extra["oidc_client"] = OpenidClient.from_issuer_url(
url="https://id.hamburg.ccc.de/realms/test/",
authentication_redirect_uri="http://localhost:8000/auth/login-callback",
@ -84,25 +90,13 @@ async def login_callback(req: Request, resp: Response, oidc_client: deps.OpenidC
# save the authentication result for later reuse
if isinstance(auth_result, TokenSuccessResponse):
now = datetime.now(UTC)
auth_start_time = datetime.fromtimestamp(float(req.cookies["auth_start_time"]), UTC)
# extract the ID token now to validate its authenticity and properly set the cookie lifetime
id_token = oidc_client.decode_id_token(auth_result.id_token, nonce=req.cookies["auth_nonce"])
# calculate how long each token is valid
at_max_age = auth_start_time - now + timedelta(seconds=auth_result.expires_in)
rt_max_age = auth_start_time - now + timedelta(seconds=auth_result.refresh_expires_in)
id_max_age = datetime.fromtimestamp(id_token.exp, UTC) - now
# update cookies
resp.set_cookie("access_token", auth_result.access_token, max_age=int(at_max_age.total_seconds()), httponly=True, secure=True)
resp.set_cookie("refresh_token", auth_result.refresh_token, max_age=int(rt_max_age.total_seconds()), httponly=True, secure=True)
resp.set_cookie("id_token", auth_result.id_token, max_age=int(id_max_age.total_seconds()), httponly=True, secure=True)
resp.set_cookie("auth_nonce", req.cookies["auth_nonce"], max_age=int(id_max_age.total_seconds()), httponly=True, secure=True)
deps.persist_auth_state(oidc_client, resp, auth_result, auth_start_time, req.cookies["auth_nonce"])
# redirect the user to the page they wanted to visit
return {"authenticated": True}
else:
return {"authenticated": False, "error": auth_result}