From 1f88f7dc28115bb3fc7a347f9bb30e7cb77ce689 Mon Sep 17 00:00:00 2001 From: etiquet Date: Sun, 19 Apr 2026 15:19:45 +0200 Subject: [PATCH 1/3] fix(auth): detect HTTPS behind TLS-terminating reverse proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When OpenRAG runs behind Traefik/Nginx that terminates TLS, the internal request arrives as HTTP. This causes the OIDC state cookie to be set without the Secure flag, which prevents the browser from sending it back on the HTTPS callback — breaking the OIDC flow. Fix: _is_request_secure() now checks: 1. PREFERRED_URL_SCHEME env var (explicit config) 2. X-Forwarded-Proto header (set by reverse proxies) 3. request.url.scheme (existing check) Without this fix, OIDC login fails with "OIDC code exchange failed" when deployed behind any TLS-terminating proxy. Co-Authored-By: Claude Opus 4.6 (1M context) --- openrag/routers/auth.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openrag/routers/auth.py b/openrag/routers/auth.py index 6613d72b..19d4e571 100644 --- a/openrag/routers/auth.py +++ b/openrag/routers/auth.py @@ -118,9 +118,16 @@ def _require_oidc_mode(): def _is_request_secure(request: Request) -> bool: """True if the client-observed scheme is HTTPS. - ``request.url.scheme`` already accounts for reverse-proxy headers when the - app is started with ``proxy_headers=True`` (see ``api.py``). + Checks multiple indicators: + 1. ``PREFERRED_URL_SCHEME`` env var (set when behind a TLS-terminating proxy) + 2. ``X-Forwarded-Proto`` header (set by reverse proxies like Traefik/Nginx) + 3. ``request.url.scheme`` (accounts for proxy_headers=True in uvicorn) """ + import os + if os.environ.get("PREFERRED_URL_SCHEME", "").lower() == "https": + return True + if request.headers.get("x-forwarded-proto", "").lower() == "https": + return True return request.url.scheme == "https" From 06c8c0a7348b185a7c6ee06b8fa9c582f65054bb Mon Sep 17 00:00:00 2001 From: EnjoyBacon7 Date: Tue, 21 Apr 2026 16:09:35 +0000 Subject: [PATCH 2/3] style: blank line after local import in _is_request_secure --- openrag/routers/auth.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openrag/routers/auth.py b/openrag/routers/auth.py index 19d4e571..120c09a1 100644 --- a/openrag/routers/auth.py +++ b/openrag/routers/auth.py @@ -124,6 +124,7 @@ def _is_request_secure(request: Request) -> bool: 3. ``request.url.scheme`` (accounts for proxy_headers=True in uvicorn) """ import os + if os.environ.get("PREFERRED_URL_SCHEME", "").lower() == "https": return True if request.headers.get("x-forwarded-proto", "").lower() == "https": From 0bf9f6ffaf9f297f577c8baa08eb32915f53d033 Mon Sep 17 00:00:00 2001 From: EnjoyBacon7 Date: Tue, 21 Apr 2026 16:15:03 +0000 Subject: [PATCH 3/3] fix: drop redundant local os import, parse chained X-Forwarded-Proto os is already imported at module scope. When a request traverses multiple proxies the header can be a comma-separated list like "https, http"; take the first (client-most) entry before comparing. --- openrag/routers/auth.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openrag/routers/auth.py b/openrag/routers/auth.py index 120c09a1..beedcc78 100644 --- a/openrag/routers/auth.py +++ b/openrag/routers/auth.py @@ -123,11 +123,12 @@ def _is_request_secure(request: Request) -> bool: 2. ``X-Forwarded-Proto`` header (set by reverse proxies like Traefik/Nginx) 3. ``request.url.scheme`` (accounts for proxy_headers=True in uvicorn) """ - import os - if os.environ.get("PREFERRED_URL_SCHEME", "").lower() == "https": return True - if request.headers.get("x-forwarded-proto", "").lower() == "https": + # X-Forwarded-Proto can be comma-separated when chained through multiple + # proxies (e.g. "https, http"); the client-most hop is the first entry. + xfp = request.headers.get("x-forwarded-proto", "") + if xfp.split(",", 1)[0].strip().lower() == "https": return True return request.url.scheme == "https"