enhancement
major
minor
defect
critical
major
minor
major
#30110
An aborted external login (OIDC) leaves a logged-in session without tokens
Observation
While using easy-planning (TopLogic 7.10.x, login via OIDC/pac4j), approximately eleven users ended up in sessions one morning where they were able to work normally but did not have UserTokens. Any feature requiring these tokens subsequently failed permanently; for example, the 3D viewer from tl-uber3d threw a NullPointerException on every render (this has since been fixed; see tl-uber-3d Issue #117, Releases 2.0.6 and 2.1.3).
In the log, all affected sessions appear alongside a failed login:
ERROR ExternalAuthenticationServlet - Login failed: The user with the loginName '…' had passed all checks and was about to be logged in. But an error occurred! … Caused by: … Commit aborted in response to long-running concurrent refetch at … UserMonitor.login(UserMonitor.java:…) at … SessionService.sendEvent(…) at … SessionService.getNewSessionForUser(…) at … Login.loginFromExternalAuth(…) at … ExternalAuthenticationServlet.loginUser(…)
A few minutes later, the message “Reusing an existing session for user '…'” appears for the same user, and the user continues working without tokens.
Cause
- SessionService.getNewSessionForUser creates the HTTP session, binds the user to it (installSession), and registers it (putSession). Only then is the LOGGED_IN event sent to the listeners. The listener that writes the login statistics (TL 7: UserMonitor, TL 8: StoreUserEventListener, configured by default) commits the transaction in the login thread.
- SessionService.sendEvent calls the listeners without error handling. If the commit fails (in this case: a refetch backlog in the database), the exception propagates all the way back to the login process. The session that is already logged in is not cleared.
- Pac4jAuthenticationServlet.loginUser does not install the OIDC tokens until after super.loginUser(…). After the exception, the session remains without tokens.
- With `reuseSession`, `ExternalAuthenticationServlet` redirects to the existing session on the next call. TL 8 additionally checks `isAuthenticatedSession`, i.e., only whether a non-anonymous user is associated with the session. This applies to half of the sessions.
TL 8 is affected in exactly the same way (tested: 8.0.0-alpha11 and the current master branch). There, the login is added to an existing session (SessionService.login with an existing registration, e.g., the anonymous session of the React interface): There, too, the user, session ID, and registration are already updated before `LOGGED_IN ` is committed.
Solution
- SessionService.sendEvent catches errors from a UserEventListener and logs them. The remaining listeners continue to be notified, and the login and logout processes complete. The login statistics are a side effect and must not prevent a login. That alone would have prevented the incident.
- ExternalAuthenticationServlet terminates the session if the login fails after the user has already been logged in. This ensures that a partially logged-in session cannot be reused via `reuseSession`, not even in the event of other errors at this point (e.g., when installing the tokens).
Affects TL 7 (backport to TL/TL_7.12_x) and TL 8 (master).
The trigger itself—the long-running refetches during easy-planning operation—is a separate issue and not part of this ticket.