[Bug]: OIDC login fails with "No session" behind reverse proxy — session cookie lost during redirect (Firefox bounce-tracking protection) #3264

Open
opened 2026-04-25 00:14:37 +02:00 by adam · 1 comment
Owner

Originally created by @miketangovictor on GitHub (Mar 16, 2026).

What happened?

OIDC authentication fails with a 400 No session error when logging in through an external identity provider (tested with Authentik). The failure is intermittent on Chrome but consistent on Firefox.

The root cause is a combination of two issues:

  1. Missing trust proxy setting — Without app.set("trust proxy", 1), Express cannot correctly interpret X-Forwarded-* headers from the reverse proxy. This causes the session cookie to behave unpredictably (incorrect secure flag inference, unreliable sameSite enforcement).

  2. Session cookie lost during OIDC redirect — Firefox's bounce-tracking protection (enabled by default since Firefox 109) strips cookies set during rapid cross-site redirects. The OIDC authorization flow redirects: ABS → Authentik → ABS callback. Firefox treats the intermediate hop as a bounce tracker and discards the session cookie before the callback route is hit. When /auth/openid/callback is reached, req.session[sessionKey] is empty, and ABS returns 400 No session with no recovery path. The session cookie also lacks sameSite: "lax" and maxAge, which contributes to cookie instability — without maxAge it is a session-scoped cookie (cleared when the tab closes or the browser considers the session ended), and without sameSite: "lax" behavior varies by browser.

What did you expect to happen?

OIDC login should complete successfully on Firefox when ABS is running behind a reverse proxy (nginx, Caddy, Cloudflare Tunnel, etc.) with an external OIDC provider such as Authentik, Authelia, or Keycloak. This is a common and well-documented deployment pattern. The OIDC callback should either preserve the session across the redirect chain or have a server-side fallback to recover state when the session cookie is not present at callback time.

Steps to reproduce the issue

  1. Deploy ABS behind a reverse proxy (nginx, Caddy, or Cloudflare Tunnel) with a valid SSL certificate
  2. Configure an OIDC provider (Authentik confirmed; likely any provider)
  3. Open ABS in Firefox (bounce-tracking protection is on by default; no need to change settings)
  4. Click "Login with SSO / OIDC"
  5. Complete authentication on the identity provider (enter credentials, approve)
  6. Observe: redirected back to ABS, receives 400 No session instead of being logged in
  7. Repeat on Chrome with identical config — login succeeds

Additional reproduction note: The failure is 100% consistent on Firefox when bounce-tracking protection is active. It can also be reproduced on Chrome by manually clearing the ABS session cookie immediately after the initial redirect to the identity provider (simulating what Firefox does automatically).

Audiobookshelf version

v2.33.0 (also confirmed on v2.32.1 and earlier)

How are you running audiobookshelf?

Docker

What OS is your Audiobookshelf server hosted from?

Linux

If the issue is being seen in the UI, what browsers are you seeing the problem on?

Firefox

Logs

# ABS server log — what appears when the callback is hit on Firefox:
# (no error is logged server-side; the 400 is returned silently)
# The session object is simply empty at callback time:
# req.session = {}   req.session[sessionKey] = undefined

Additional Notes

The fix involves three changes to the server:

  1. Server.js — Add trust proxy and harden the session cookie:
    const app = express()
    app.set("trust proxy", 1) // Trust X-Forwarded-* from reverse proxy

// In expressSession cookie config:
cookie: {
secure: false,
sameSite: "lax", // Prevent cookie from being withheld on same-site redirects
maxAge: 86400000 // Persist cookie for 24h (not session-scoped)
}

  1. OidcAuthStrategy.js — Persist web-flow state in openIdAuthSession as a server-side fallback:
    // After req.session[sessionKey] = { state, code_verifier, ... } is set:
    if (!isMobileFlow) {
    this.openIdAuthSession.set(state, {
    __webState: { state, max_age, response_type, code_verifier, mobile: undefined, sso_redirect_uri },
    __callbackUrl: req.query.redirect_uri || req.query.callback || null
    })
    }

  2. Auth.js — At the callback route, fall back to openIdAuthSession if session cookie is missing:
    if (!req.session[sessionKey]) {
    const stateParam = req.query.state
    if (stateParam && this.oidcAuthStrategy.openIdAuthSession.has(stateParam)) {
    const stored = this.oidcAuthStrategy.openIdAuthSession.get(stateParam)
    if (stored.__webState) {
    req.session[sessionKey] = stored.__webState
    if (!req.cookies.auth_cb && stored.__callbackUrl) {
    req.cookies.auth_cb = stored.__callbackUrl
    }
    this.oidcAuthStrategy.openIdAuthSession.delete(stateParam)
    } else {
    return res.status(400).send('No session')
    }
    } else {
    return res.status(400).send('No session')
    }
    }

This fix has been running in production on ABS v2.32.1 and v2.33.0 with Authentik as the OIDC provider and Firefox as
the primary client, with no regressions observed. The openIdAuthSession map already exists in the codebase for
mobile-flow state — this reuses it for web-flow recovery.

Related: Issue #4630 ("re-login very often") may be partially caused by this same failure mode, as the symptom
(sporadic login failures) matches session cookie loss on Firefox.

Originally created by @miketangovictor on GitHub (Mar 16, 2026). ### What happened? OIDC authentication fails with a 400 No session error when logging in through an external identity provider (tested with Authentik). The failure is intermittent on Chrome but consistent on Firefox. The root cause is a combination of two issues: 1. Missing trust proxy setting — Without app.set("trust proxy", 1), Express cannot correctly interpret X-Forwarded-* headers from the reverse proxy. This causes the session cookie to behave unpredictably (incorrect secure flag inference, unreliable sameSite enforcement). 2. Session cookie lost during OIDC redirect — Firefox's bounce-tracking protection (enabled by default since Firefox 109) strips cookies set during rapid cross-site redirects. The OIDC authorization flow redirects: ABS → Authentik → ABS callback. Firefox treats the intermediate hop as a bounce tracker and discards the session cookie before the callback route is hit. When /auth/openid/callback is reached, req.session[sessionKey] is empty, and ABS returns 400 No session with no recovery path. The session cookie also lacks sameSite: "lax" and maxAge, which contributes to cookie instability — without maxAge it is a session-scoped cookie (cleared when the tab closes or the browser considers the session ended), and without sameSite: "lax" behavior varies by browser. ### What did you expect to happen? OIDC login should complete successfully on Firefox when ABS is running behind a reverse proxy (nginx, Caddy, Cloudflare Tunnel, etc.) with an external OIDC provider such as Authentik, Authelia, or Keycloak. This is a common and well-documented deployment pattern. The OIDC callback should either preserve the session across the redirect chain or have a server-side fallback to recover state when the session cookie is not present at callback time. ### Steps to reproduce the issue 1. Deploy ABS behind a reverse proxy (nginx, Caddy, or Cloudflare Tunnel) with a valid SSL certificate 2. Configure an OIDC provider (Authentik confirmed; likely any provider) 3. Open ABS in Firefox (bounce-tracking protection is on by default; no need to change settings) 4. Click "Login with SSO / OIDC" 5. Complete authentication on the identity provider (enter credentials, approve) 6. Observe: redirected back to ABS, receives 400 No session instead of being logged in 7. Repeat on Chrome with identical config — login succeeds Additional reproduction note: The failure is 100% consistent on Firefox when bounce-tracking protection is active. It can also be reproduced on Chrome by manually clearing the ABS session cookie immediately after the initial redirect to the identity provider (simulating what Firefox does automatically). ### Audiobookshelf version v2.33.0 (also confirmed on v2.32.1 and earlier) ### How are you running audiobookshelf? Docker ### What OS is your Audiobookshelf server hosted from? Linux ### If the issue is being seen in the UI, what browsers are you seeing the problem on? Firefox ### Logs ```shell # ABS server log — what appears when the callback is hit on Firefox: # (no error is logged server-side; the 400 is returned silently) # The session object is simply empty at callback time: # req.session = {} req.session[sessionKey] = undefined ``` ### Additional Notes The fix involves three changes to the server: 1. Server.js — Add trust proxy and harden the session cookie: const app = express() app.set("trust proxy", 1) // Trust X-Forwarded-* from reverse proxy // In expressSession cookie config: cookie: { secure: false, sameSite: "lax", // Prevent cookie from being withheld on same-site redirects maxAge: 86400000 // Persist cookie for 24h (not session-scoped) } 2. OidcAuthStrategy.js — Persist web-flow state in openIdAuthSession as a server-side fallback: // After req.session[sessionKey] = { state, code_verifier, ... } is set: if (!isMobileFlow) { this.openIdAuthSession.set(state, { __webState: { state, max_age, response_type, code_verifier, mobile: undefined, sso_redirect_uri }, __callbackUrl: req.query.redirect_uri || req.query.callback || null }) } 3. Auth.js — At the callback route, fall back to openIdAuthSession if session cookie is missing: if (!req.session[sessionKey]) { const stateParam = req.query.state if (stateParam && this.oidcAuthStrategy.openIdAuthSession.has(stateParam)) { const stored = this.oidcAuthStrategy.openIdAuthSession.get(stateParam) if (stored.__webState) { req.session[sessionKey] = stored.__webState if (!req.cookies.auth_cb && stored.__callbackUrl) { req.cookies.auth_cb = stored.__callbackUrl } this.oidcAuthStrategy.openIdAuthSession.delete(stateParam) } else { return res.status(400).send('No session') } } else { return res.status(400).send('No session') } } This fix has been running in production on ABS v2.32.1 and v2.33.0 with Authentik as the OIDC provider and Firefox as the primary client, with no regressions observed. The openIdAuthSession map already exists in the codebase for mobile-flow state — this reuses it for web-flow recovery. Related: Issue #4630 ("re-login very often") may be partially caused by this same failure mode, as the symptom (sporadic login failures) matches session cookie loss on Firefox.
adam added the bug label 2026-04-25 00:14:37 +02:00
Author
Owner

@miketangovictor commented on GitHub (Mar 18, 2026):

Adding a comment here that when I ran into this issue I had Claude Code work through the solution and then create the bug submission. It appears that this is something that had been identified back with issue #4630 and the script that was written and included above did fix the issue, but curious if others have experienced this or if there is anything about this fix that would be problematic.

@miketangovictor commented on GitHub (Mar 18, 2026): Adding a comment here that when I ran into this issue I had Claude Code work through the solution and then create the bug submission. It appears that this is something that had been identified back with issue #4630 and the script that was written and included above did fix the issue, but curious if others have experienced this or if there is anything about this fix that would be problematic.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf#3264