From 09478222ceedf22f18951fcabd722599602d168c Mon Sep 17 00:00:00 2001 From: Cron Date: Fri, 24 Jul 2026 10:55:36 -0500 Subject: [PATCH] Docker/OIDC: redirect to a default URL on expired auth state instead of 400-ing (#9143) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem With OIDC on the official Docker image, interactive SSO logins intermittently return `HTTP 400` at the `/i/oidc/` callback. The failure tracks login duration. `mod_auth_openidc` sets an anti-CSRF state cookie bounded by `OIDCStateTimeout` (default 300 s). If a login through an external IdP takes longer than that (MFA, a slow provider screen, the user stepping away), the cookie is already gone when the callback returns. The image does not set `OIDCDefaultURL`, so the module has no fallback and returns an error instead of restarting the login. Apache log: ``` oidc_response_proto_state_restore: state has expired oidc_response_process: invalid authorization response state and no default SSO URL is set, sending an error... → GET /i/oidc/?code=…&state=… 400 ``` This is [documented mod_auth_openidc behaviour](https://github.com/OpenIDC/mod_auth_openidc/blob/72c9f479c2d228477ff0a9518964f61879c83fb6/auth_openidc.conf#L723-L733) when `OIDCDefaultURL` is unset. Both `OIDCDefaultURL` and `OIDCStateTimeout` are `RSRC_CONF` (server config only), so the `./FreshRSS/p/i/.htaccess` escape hatch cannot set them and the fix has to live in the image. Reproduce: log out, start SSO, wait more than 5 minutes on the IdP screen, complete. ## Fix Add `OIDC_DEFAULT_URL`, wired through the existing `${OIDC_*}` env pattern, defaulting to `/i/` (the index, consistent with `OIDCRedirectURI /i/oidc/`). On an expired or unmatchable state the module now redirects to `/i/`, which is protected and starts a fresh login against the existing IdP session. `OIDCStateTimeout` stays at 300 s; the window is fine, the missing fallback was the bug. `OIDC_STATE_TIMEOUT` is intentionally not exposed. It can be added the same way if there is a reason to tune the window. * Docker/OIDC: redirect to a default URL on expired auth state instead of 400-ing Adds OIDC_DEFAULT_URL (default /i/), wired through the existing ${OIDC_*} env pattern, so an expired or unmatchable mod_auth_openidc state redirects to the FreshRSS index and restarts the login instead of returning HTTP 400. * git commit update --------- Co-authored-by: Alexandre Alapetite --- Docker/FreshRSS.Apache.conf | 1 + Docker/entrypoint.sh | 1 + docs/en/admins/16_OpenID-Connect.md | 1 + 3 files changed, 3 insertions(+) diff --git a/Docker/FreshRSS.Apache.conf b/Docker/FreshRSS.Apache.conf index 5db17e91a..47587889a 100644 --- a/Docker/FreshRSS.Apache.conf +++ b/Docker/FreshRSS.Apache.conf @@ -40,6 +40,7 @@ CustomLog "|/var/www/FreshRSS/cli/sensitive-log.sh" combined_proxy OIDCSessionType ${OIDC_SESSION_TYPE} OIDCRedirectURI /i/oidc/ + OIDCDefaultURL ${OIDC_DEFAULT_URL} OIDCCryptoPassphrase ${OIDC_CLIENT_CRYPTO_KEY} Define "Test_${OIDC_REMOTE_USER_CLAIM}" diff --git a/Docker/entrypoint.sh b/Docker/entrypoint.sh index b7c06c672..a79853d66 100755 --- a/Docker/entrypoint.sh +++ b/Docker/entrypoint.sh @@ -43,6 +43,7 @@ if [ -n "$OIDC_ENABLED" ] && [ "$OIDC_ENABLED" -ne 0 ]; then export OIDC_SESSION_INACTIVITY_TIMEOUT="${OIDC_SESSION_INACTIVITY_TIMEOUT:-300}" export OIDC_SESSION_MAX_DURATION="${OIDC_SESSION_MAX_DURATION:-27200}" export OIDC_SESSION_TYPE="${OIDC_SESSION_TYPE:-server-cache}" + export OIDC_DEFAULT_URL="${OIDC_DEFAULT_URL:-/i/}" # Debian (which a2enmod >/dev/null && a2enmod -q auth_openidc) || diff --git a/docs/en/admins/16_OpenID-Connect.md b/docs/en/admins/16_OpenID-Connect.md index da2488748..0394dd09c 100644 --- a/docs/en/admins/16_OpenID-Connect.md +++ b/docs/en/admins/16_OpenID-Connect.md @@ -46,6 +46,7 @@ OIDC support in Docker is activated by the presence of a non-empty non-zero `OID * `OIDC_SESSION_INACTIVITY_TIMEOUT`: Optional. Interval in seconds after which the session will be invalidated when no interaction has occurred. When not defined, the default is 300 seconds. * `OIDC_SESSION_MAX_DURATION`: Optional. Maximum duration of the application session. When not defined the default is 8 hours (3600 * 8 seconds). When set to 0, the session duration will be set equal to the expiry time of the ID token. * `OIDC_SESSION_TYPE`: Optional. OpenID Connect session storage type. See [mod_auth_openidc’s documentation for details](https://github.com/OpenIDC/mod_auth_openidc/blob/72c9f479c2d228477ff0a9518964f61879c83fb6/auth_openidc.conf#L587-L596). +* `OIDC_DEFAULT_URL`: Optional. URL the user is redirected to when the OIDC module receives a callback whose anti-CSRF state has expired or cannot be matched, for example when an interactive login (external identity provider, MFA/step-up, or the user stepping away) takes longer than the state timeout. When not defined, the default is `/i/`, the FreshRSS index, so that an expired login restarts against the existing provider session instead of returning an HTTP 400 error page. Maps to mod_auth_openidc’s [`OIDCDefaultURL`](https://github.com/OpenIDC/mod_auth_openidc/blob/b2e99151bc695335089c8d4bfe5793624ac0732e/auth_openidc.conf#L786-L790). You may add additional custom configuration in a new `./FreshRSS/p/i/.htaccess` file.