From e1f13abc7f862771b1dbe5787cd26da67bd11e7f Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 24 Feb 2026 00:50:24 +0000 Subject: [PATCH] Fix middleware ordering: device_id must be outermost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FastAPI runs the last-registered middleware first on request. device_id_middleware was inner, so silent_auth_check's early redirect bypassed it — cookie never set. Co-Authored-By: Claude Opus 4.6 --- app/__init__.py | 50 +++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 6d4b202..4ea0281 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -49,31 +49,8 @@ def create_app() -> FastAPI: async def shutdown(): await close_db() - # Device ID middleware — track browser identity across domains - @app.middleware("http") - async def device_id_middleware(request: Request, call_next): - did = request.cookies.get(_DEVICE_COOKIE) - if did: - request.state.device_id = did - request.state._new_device_id = False - else: - request.state.device_id = secrets.token_urlsafe(32) - request.state._new_device_id = True - - response = await call_next(request) - - if getattr(request.state, "_new_device_id", False): - response.set_cookie( - key=_DEVICE_COOKIE, - value=request.state.device_id, - max_age=_DEVICE_COOKIE_MAX_AGE, - httponly=True, - samesite="lax", - secure=True, - ) - return response - # Silent auth check — auto-login via prompt=none OAuth + # NOTE: registered BEFORE device_id so device_id is outermost (runs first) @app.middleware("http") async def silent_auth_check(request: Request, call_next): path = request.url.path @@ -121,6 +98,31 @@ def create_app() -> FastAPI: status_code=302, ) + # Device ID middleware — track browser identity across domains + # Registered AFTER silent_auth_check so it's outermost (always runs) + @app.middleware("http") + async def device_id_middleware(request: Request, call_next): + did = request.cookies.get(_DEVICE_COOKIE) + if did: + request.state.device_id = did + request.state._new_device_id = False + else: + request.state.device_id = secrets.token_urlsafe(32) + request.state._new_device_id = True + + response = await call_next(request) + + if getattr(request.state, "_new_device_id", False): + response.set_cookie( + key=_DEVICE_COOKIE, + value=request.state.device_id, + max_age=_DEVICE_COOKIE_MAX_AGE, + httponly=True, + samesite="lax", + secure=True, + ) + return response + # Initialize Jinja2 templates template_dir = Path(__file__).parent / "templates" app.state.templates = create_jinja_env(template_dir)