# Disallow simultaneous logins

**URL:** <https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108>\
**Category:** JupyterHub\
**Created:** [November 7, 2025, 3:41pm UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108 "2025-11-07T15:41:21Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![David\_Carvalho](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/david_carvalho/32/15861_2.png) [@David\_Carvalho](https://discourse.jupyter.org/u/David_Carvalho)\
**Post date:** [November 7, 2025, 3:41pm UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/1 "2025-11-07T15:41:21Z")

</div>

Hi.

I’m using JupyterHub to support a classroom. The accounts are created in the Linux system, and then I allow the login from the jupyterhub interface (add user). We want to use this for grading purposes, so it would be extremely useful if we could limit one login per user at any time. For now I’m using firewall rules to block access outside the lab network, but it would be great to achieve this. From the several suggestions provided by Copilot, google AI, etc, no matter what, I always get: 2 logins are always allowed; or 500: internal server error and no one can login. This is the latest code I’m using:

_from jupyterhub.auth import PAMAuthenticator  
from jupyterhub.orm import User_

\*class SingleSessionPAMAuthenticator(PAMAuthenticator):  
async def authenticate(self, handler, data=None):  
username = await super().authenticate(handler, data)  
if username:  
user = self.db.query(User).filter(User.name == username).first()  
if user:

# Check if user has any active sessions

if user.sessions:

# Deny login if already logged in

self.log.warning(f"User {username} already has an active session.")  
return None # This causes login to fail  
return username\*

_c.JupyterHub.authenticator\_class = SingleSessionPAMAuthenticator_

Has anyone solved this before?

Thanks and regards.

---

<div class="post-metadata">

**Author:** ![rgaiacs](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/rgaiacs/32/20200_2.png) [@rgaiacs](https://discourse.jupyter.org/u/rgaiacs)\
**Post date:** [November 7, 2025, 4:08pm UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/2 "2025-11-07T16:08:27Z")

</div>

Hi @David_Carvalho,

Thanks for the question.

One of the features of JupyterHub is that it can delegate the authentication, see [Authenticators — JupyterHub documentation](https://jupyterhub.readthedocs.io/en/stable/reference/authenticators.html#the-oauthenticator) . My position is that instead of add features to the default PAM Authenticator, JupyterHub operators should select and run their own authenticator.

[Based on StackOverflow](https://stackoverflow.com/a/60197078/1802726), the feature that you are inquiring is [implemented Keycloak](https://github.com/keycloak/keycloak/issues/10077).

---

<div class="post-metadata">

**Author:** ![David\_Carvalho](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/david_carvalho/32/15861_2.png) [@David\_Carvalho](https://discourse.jupyter.org/u/David_Carvalho)\
**Post date:** [November 10, 2025, 9:57am UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/3 "2025-11-10T09:57:20Z")

</div>

Thank you so much. I’ll look into it and hopefully it will be more useful than all the AI suggestions I’ve tried. I’ll post the results. Thanks once again.

---

<div class="post-metadata">

**Author:** ![David\_Carvalho](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/david_carvalho/32/15861_2.png) [@David\_Carvalho](https://discourse.jupyter.org/u/David_Carvalho)\
**Post date:** [November 10, 2025, 2:35pm UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/4 "2025-11-10T14:35:17Z")

</div>

For what I understand, this is an application to manage logins in behalf of others. This would add so much complexity …I wonder if this can be done only with the builtin authenticator. All the options I’ve tried either don’t allow anyone or they simply don’t restrict simultaneous logins.

---

<div class="post-metadata">

**Author:** ![minrk](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/minrk/32/13_2.png) [@minrk](https://discourse.jupyter.org/u/minrk)\
**Post date:** [November 10, 2025, 5:10pm UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/5 "2025-11-10T17:10:30Z")

</div>

From an implementation standpoint, this could be tricky without modifications to JupyterHub.

There’s no real way to know if there are other _active_ sessions (as opposed to an opened session from a browser whose cookies are cleared), but we can revoke them by generating a fresh cookie id on successful login. This isn’t the _most_ public of APIs, but an Authenticator that defines a custom LoginHandler, which overrides `set_login_cookie` to something like:

```python
def set_login_cookie(self, user):
    # setting a new cookie id invalidates all existing login cookies
    # before setting the new one
    user.cookie_id = secrets.token_urlsafe(32)
    return super().set_login_cookie(user)

```

This will only affect sessions with the Hub, though, it won’t immediately invalidate OAuth tokens used to access the single-user server. That’s a more substantial task.

---

<div class="post-metadata">

**Author:** ![David\_Carvalho](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/david_carvalho/32/15861_2.png) [@David\_Carvalho](https://discourse.jupyter.org/u/David_Carvalho)\
**Post date:** [November 11, 2025, 11:19am UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/6 "2025-11-11T11:19:23Z")

</div>

Thanks. My system is using PAM. I’ve already set it up to allow only 1 login at the time, but it is only working for ssh, not the jupyterhub interface.

/etc/security/limits.conf - @mygroup hard maxlogins 1

/etc/pam.d/login - session required pam\_limits.so

so, even I didn’t change anything in /etc/pam.d/sshd, it is restricting more than 1 login at the time for ssh. Should it be working for jupyterhub also, as it is using PAM?

---

<div class="post-metadata">

**Author:** ![David\_Carvalho](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/david_carvalho/32/15861_2.png) [@David\_Carvalho](https://discourse.jupyter.org/u/David_Carvalho)\
**Post date:** [November 11, 2025, 5:03pm UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/7 "2025-11-11T17:03:56Z")

</div>

> [@minrk](#):
>
> ```auto
> def set_login_cookie(self, user):
> # setting a new cookie id invalidates all existing login cookies
> # before setting the new one
> user.cookie_id = secrets.token_urlsafe(32)
> return super().set_login_cookie(user)
> 
> ```

I could still login from 2 different browsers.

---

<div class="post-metadata">

**Author:** ![minrk](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/minrk/32/13_2.png) [@minrk](https://discourse.jupyter.org/u/minrk)\
**Post date:** [November 11, 2025, 5:10pm UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/8 "2025-11-11T17:10:41Z")

</div>

> [@David\_Carvalho](#):
>
> Should it be working for jupyterhub also, as it is using PAM?

No, I would not expect that. Because a browser cookie is not like a login shell session. The server doesn’t really have an understanding of ‘open sessions’, and they can be ended by the browser without the server ever knowing about it.

> I could still login from 2 different browsers.

Do you want the old session to prevent new sessions, or the new session to revoke old sessions? If it’s the former, that just can’t work for browsers because there’s no way for the server to know if the old session is still around or not. If it’s the latter, resetting the cookie id should do that for the Hub cookie, but won’t affect jupyterlab cookies (it is possible to revoke jupyterlab cookies from not-this-session, but not via public APIs, you need to perform some db operations).

---

<div class="post-metadata">

**Author:** ![David\_Carvalho](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/david_carvalho/32/15861_2.png) [@David\_Carvalho](https://discourse.jupyter.org/u/David_Carvalho)\
**Post date:** [November 12, 2025, 9:49am UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/9 "2025-11-12T09:49:06Z")

</div>

Thank you so much for the reply. Ideally I was looking for the old session to prevent new sessions. My knowledge about jupyterhub is minimal to mess with jupyterlab cookies. For now, the firewall is my only solution to prevent outside users to login during exams.

Thanks anyway.

---

<div class="post-metadata">

**Author:** ![David\_Carvalho](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/david_carvalho/32/15861_2.png) [@David\_Carvalho](https://discourse.jupyter.org/u/David_Carvalho)\
**Post date:** [November 12, 2025, 2:58pm UTC](https://discourse.jupyter.org/t/disallow-simultaneous-logins/38108/10 "2025-11-12T14:58:04Z")

</div>

I was trying to find a solution “outside” JupyterHUB, but so far it is not working.

Mantaining all my configuration, I added: c.PAMAuthenticator.service = ‘my\_login\_jupyterhub’. This was a copy of /etc/pam.d/login with one new entry: session optional pam\_exec.so /root/sessions/kill\_existing\_sessions.sh.

This script has the following code, but this solution didn’t work either. It still allowed simultaneous logins:

#!/bin/bash

USERNAME=$PAM\_USER

if [-z “$USERNAME”]; then

exit 0  
fi

```auto
pkill -u "$USERNAME" -f jupyterhub-singleuser

```

exit 0
