Summary
When FEATURES['PREVENT_CONCURRENT_LOGINS'] is enabled, any user account that is shared by more than one concurrent client becomes unusable, because every login deletes the account's previously-registered session. There is no way to exempt legitimate shared/service accounts (e.g. the xqueue-watcher grader account), so they fall into a self-sustaining login storm.
Mechanism
enforce_single_login (common/djangoapps/student/models/user.py) runs on every user_logged_in and calls UserProfile.set_login_session():
def set_login_session(self, session_id=None):
meta = self.get_meta()
old_login = meta.get('session_id', None)
if old_login:
SessionStore(session_key=old_login).delete() # deletes the prior session
meta['session_id'] = session_id
self.set_meta(meta)
self.save()
The profile holds a single session_id slot per user. For human users this is the intended "one active session" behavior. But when multiple clients authenticate as the same account, each login deletes the other clients' sessions. Each client then gets 401/403 on its next request, re-authenticates, and in doing so evicts another client — a continuous storm.
Real-world impact: xqueue-watcher
xqueue-watcher runs many workers (multiple replicas × multiple queues × CONNECTIONS) that all authenticate as one shared LMS service account. With PREVENT_CONCURRENT_LOGINS on, those workers continuously evict each other's sessions, producing:
- a login storm (thousands of logins/hour for one account),
- ~1:1
Forbidden: /xqueue/get_submission/ and /xqueue/put_result/ responses,
- stalled external grading.
We confirmed this in production and QA via a faithful reproduction: a fresh service-account session authenticates for a few seconds, then its session row is deleted from the session cache by the next login of the same account → 403. (We ruled out cache eviction, cookie TTL, password-hash drift, and SECRET_KEY skew — the deletion is set_login_session.)
Proposed fix
Add an opt-in exemption so specific service/automation accounts can be excluded from single-login enforcement, via two new settings (both default empty, so behavior is unchanged unless configured):
SINGLE_LOGIN_EXEMPT_USERNAMES
SINGLE_LOGIN_EXEMPT_GROUPS
enforce_single_login returns early for exempt users, leaving PREVENT_CONCURRENT_LOGINS fully in force for everyone else. PR to follow.
Steps to reproduce
- Set
FEATURES['PREVENT_CONCURRENT_LOGINS'] = True.
- Authenticate as the same user from two clients; make an authenticated request from the first.
- The first client is logged out (session deleted). Repeat rapidly from N clients → login storm.
Summary
When
FEATURES['PREVENT_CONCURRENT_LOGINS']is enabled, any user account that is shared by more than one concurrent client becomes unusable, because every login deletes the account's previously-registered session. There is no way to exempt legitimate shared/service accounts (e.g. thexqueue-watchergrader account), so they fall into a self-sustaining login storm.Mechanism
enforce_single_login(common/djangoapps/student/models/user.py) runs on everyuser_logged_inand callsUserProfile.set_login_session():The profile holds a single
session_idslot per user. For human users this is the intended "one active session" behavior. But when multiple clients authenticate as the same account, each login deletes the other clients' sessions. Each client then gets401/403on its next request, re-authenticates, and in doing so evicts another client — a continuous storm.Real-world impact: xqueue-watcher
xqueue-watcherruns many workers (multiple replicas × multiple queues ×CONNECTIONS) that all authenticate as one shared LMS service account. WithPREVENT_CONCURRENT_LOGINSon, those workers continuously evict each other's sessions, producing:Forbidden: /xqueue/get_submission/and/xqueue/put_result/responses,We confirmed this in production and QA via a faithful reproduction: a fresh service-account session authenticates for a few seconds, then its session row is deleted from the session cache by the next login of the same account →
403. (We ruled out cache eviction, cookie TTL, password-hash drift, and SECRET_KEY skew — the deletion isset_login_session.)Proposed fix
Add an opt-in exemption so specific service/automation accounts can be excluded from single-login enforcement, via two new settings (both default empty, so behavior is unchanged unless configured):
SINGLE_LOGIN_EXEMPT_USERNAMESSINGLE_LOGIN_EXEMPT_GROUPSenforce_single_loginreturns early for exempt users, leavingPREVENT_CONCURRENT_LOGINSfully in force for everyone else. PR to follow.Steps to reproduce
FEATURES['PREVENT_CONCURRENT_LOGINS'] = True.