Symptom
After re-provisioning a pod (or otherwise rotating cookie-signing keys), a browser that still holds an old _session / _interaction cookie for the IdP origin fails sign-in at the consent / "Allow access" step. provider.interactionFinished(...) throws and the request 400s:
SessionNotFound: invalid_request — "session not found"
at #getInteraction (oidc-provider/lib/provider.js)
at Provider.interactionResult / interactionFinished
"Promise errored, but reply.sent = true was set"
Login + the interaction pages all succeed; only the final confirm (interactionFinished) dies. The user is stuck until they manually clear site data for the IdP origin. Incognito works (no stale cookie), which pinpoints it.
Repro
- Sign in to a pod, leave the browser cookies in place.
- Re-provision / reinstall so the persisted cookie keys change (or the stored session/interaction is gone).
- Sign in again from the same (non-incognito) browser → reaches consent → "Allow access" →
SessionNotFound.
(Surfaced repeatedly during the Android app dev cycle — js-pod/android — across reinstalls; a fresh user wouldn't hit it, but a reinstall does.)
Why it matters
A stale cookie shouldn't be a dead end. The user has no in-app way to recover; they have to know to clear site data, which a normal user won't.
Existing precedent (the fix is small)
JSS already handles the analogous deleted-account case and already has the helpers:
src/idp/provider.js:268,407,420 — guard that, when a session/grant is missing due to stale cookies, calls expireSessionCookiesKoa(ctx) and redirects back to retry.
src/idp/cookies.js — expireSessionCookies(reply, request) and expireSessionCookiesKoa(ctx).
src/idp/interactions.js:221-224 — already wraps one interactionFinished in try/catch with a fallback ("interactionFinished failed, using fallback").
Proposed fix
In the interaction handlers that call provider.interactionFinished(...) (src/idp/interactions.js — the /confirm path via src/idp/index.js:408, plus the other call sites at lines ~166, 289, 606, 644, 823), catch SessionNotFound (err.name === 'SessionNotFound') and, instead of erroring, expire the session/interaction cookies and 303 back to the authorization URL to restart the interaction cleanly — mirroring the existing deleted-account guard. Net effect: a stale cookie self-heals into a fresh login instead of a 400.
Symptom
After re-provisioning a pod (or otherwise rotating cookie-signing keys), a browser that still holds an old
_session/_interactioncookie for the IdP origin fails sign-in at the consent / "Allow access" step.provider.interactionFinished(...)throws and the request 400s:Login + the interaction pages all succeed; only the final
confirm(interactionFinished) dies. The user is stuck until they manually clear site data for the IdP origin. Incognito works (no stale cookie), which pinpoints it.Repro
SessionNotFound.(Surfaced repeatedly during the Android app dev cycle — js-pod/android — across reinstalls; a fresh user wouldn't hit it, but a reinstall does.)
Why it matters
A stale cookie shouldn't be a dead end. The user has no in-app way to recover; they have to know to clear site data, which a normal user won't.
Existing precedent (the fix is small)
JSS already handles the analogous deleted-account case and already has the helpers:
src/idp/provider.js:268,407,420— guard that, when a session/grant is missing due to stale cookies, callsexpireSessionCookiesKoa(ctx)and redirects back to retry.src/idp/cookies.js—expireSessionCookies(reply, request)andexpireSessionCookiesKoa(ctx).src/idp/interactions.js:221-224— already wraps oneinteractionFinishedin try/catch with a fallback ("interactionFinished failed, using fallback").Proposed fix
In the interaction handlers that call
provider.interactionFinished(...)(src/idp/interactions.js— the/confirmpath viasrc/idp/index.js:408, plus the other call sites at lines ~166, 289, 606, 644, 823), catchSessionNotFound(err.name === 'SessionNotFound') and, instead of erroring, expire the session/interaction cookies and 303 back to the authorization URL to restart the interaction cleanly — mirroring the existing deleted-account guard. Net effect: a stale cookie self-heals into a fresh login instead of a 400.