From bfd8d55f2725fff6f66de063dd4708e5de5fcaa2 Mon Sep 17 00:00:00 2001 From: giles Date: Mon, 23 Feb 2026 11:23:26 +0000 Subject: [PATCH] Silent SSO via sso_hint cookie - Federation sets sso_hint=1 on .rose-ash.com after magic link login - Client apps: before_request checks sso_hint, triggers silent OAuth once per session (sso_checked flag prevents loops) - Logout clears sso_hint cookie on all apps Co-Authored-By: Claude Opus 4.6 --- infrastructure/factory.py | 20 +++++++++++++++++++- infrastructure/oauth.py | 6 ++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/infrastructure/factory.py b/infrastructure/factory.py index 114c9dc..85553c6 100644 --- a/infrastructure/factory.py +++ b/infrastructure/factory.py @@ -5,7 +5,7 @@ import os from pathlib import Path from typing import Callable, Awaitable, Sequence -from quart import Quart, request, g, send_from_directory +from quart import Quart, request, g, redirect, send_from_directory from shared.config import init_config, config, pretty from shared.models import KV # ensure shared models imported @@ -122,6 +122,24 @@ def create_base_app( for fn in before_request_fns: app.before_request(fn) + # Silent SSO: if federation set sso_hint cookie, trigger OAuth once + if name != "federation": + from urllib.parse import quote as _quote + + @app.before_request + async def _sso_check(): + from quart import session as qs + if request.path.startswith("/auth/"): + return + if qs.get("uid"): + return + if qs.get("sso_checked"): + return + if not request.cookies.get("sso_hint"): + return + qs["sso_checked"] = True + return redirect(f"/auth/login/?next={_quote(request.url, safe='')}") + @app.before_request async def _csrf_protect(): await protect() diff --git a/infrastructure/oauth.py b/infrastructure/oauth.py index d105ec0..27d0449 100644 --- a/infrastructure/oauth.py +++ b/infrastructure/oauth.py @@ -125,8 +125,10 @@ def create_oauth_blueprint(app_name: str) -> Blueprint: async def logout(): qsession.pop(SESSION_USER_KEY, None) qsession.pop("cart_sid", None) - # Redirect to blog home — avoids re-auth loop on apps that require login + qsession.pop("sso_checked", None) from shared.infrastructure.urls import blog_url - return redirect(blog_url("/")) + resp = redirect(blog_url("/")) + resp.delete_cookie("sso_hint", domain=".rose-ash.com", path="/") + return resp return bp