Jupyterhub, Swarmspawner and CAS : default_url not working (Error 500)

Hello everyone.
A new problem in my Jupyter Hub configuration, which relies on a swarm cluster (via SwarmSpawner) with CAS authentication.
My problem is that after authentication, the user should normally arrive at the /hub/spawn page where the list of available notebooks is located.
Then they should choose a notebook and start their server. But that’s not what happens: after authentication, the user is immediately redirected to a page where the CAS ticket is located: https:///hub/login?ticket=CAS ticket-CAS URL. This page seems to want to open a link to a container (the last one opened, it seems) that isn’t started yet. This causes a 500 error.
How can I prevent the HUB from redirecting the user to this page instead of to the list of containers to start?

PS: I don’t have anything relevant in the logs, except for an error message indicating that the user cannot be created on the system because it already exists.
Newly created users don’t have this problem because they haven’t started any containers yet.

here is my config:

import os
import shutil
import subprocess
import pwd
import grp
import logging
from dockerspawner import SwarmSpawner
import sys

c.ConfigurableHTTPProxy.should_start = False
c.ConfigurableHTTPProxy.api_url = 'http://proxy:8001'
c.LocalAuthenticator.create_system_users = True

class MySwarmSpawner(SwarmSpawner):
    def start(self):
        # specific cmd for speciffic images
        image_name = self.user_options.get('image')
        if image_name == '127.0.0.1:5000/datascience-notebook-capsule': 
            self.cmd = ['start-notebook.sh']
        else:
            self.cmd = ['jupyter-labhub']
        return super().start()
        
    def create_object(self):
        username = self.user.name  # get the username
        jupyter_username = 'jupyter-' + username
        # get UID/GID
        uid = pwd.getpwnam(jupyter_username).pw_uid
        gid = grp.getgrnam(jupyter_username).gr_gid      
        self.extra_container_spec.update({"user": f"{uid}:{gid}"})
        return super().create_object()

def create_dir_hook(spawner):
    """Create directory and set permissions"""
    username = spawner.user.name  # get the username
    jupyter_username = 'jupyter-' + username
    volume_path = os.path.join('/home', jupyter_username)
    uid = pwd.getpwnam(jupyter_username).pw_uid
    gid = grp.getgrnam(jupyter_username).gr_gid
    logging.info(f"Hook création dossier avec UID/GID : {uid}:{gid}")
    if not os.path.exists(volume_path):
        os.makedirs(volume_path, mode=0o755, exist_ok=True)
        src_path = '/home_src/jovyan/'
        if os.path.exists(src_path):
            shutil.copytree(src_path, volume_path, dirs_exist_ok=True)
    subprocess.run(['chown', '-R', f'{uid}:{gid}', volume_path], check=True)
    subprocess.run(['chmod', '-R', 'u+rwX', volume_path], check=True)
    
def clean_dir_hook(spawner):
    """Delete directory"""
    username = spawner.user.name  # get the username
    temp_path = os.path.join('/home', username, 'temp')
    if os.path.exists(temp_path) and os.path.isdir(temp_path):
        shutil.rmtree(temp_path)

c.Spawner.pre_spawn_hook = create_dir_hook
c.Spawner.post_stop_hook = clean_dir_hook

# Volumes
c.SwarmSpawner.volumes = {
    '/data/2025/prod.CAS/home/jupyter-{username}': '/home/jovyan',
}

c.JupyterHub.spawner_class = MySwarmSpawner

c.JupyterHub.hub_ip = '0.0.0.0'
c.JupyterHub.hub_connect_ip = 'hub'
c.SwarmSpawner.network_name = 'jupyterhub-net'
c.SwarmSpawner.extra_host_config = {'network_mode': 'jupyterhub-net'}


c.JupyterHub.shutdown_on_logout = True
c.JupyterHub.cleanup_servers = True
c.JupyterHub.allow_named_servers = False
c.JupyterHub.named_server_limit_per_user = 1
c.JupyterHub.default_url = '/hub/spawn'
c.JupyterHub.default_server_name = ''  # no automatic redirection
c.JupyterHub.redirect_to_server = False  # block auto redirection after login


# Logging DEBUG
c.JupyterHub.log_level = logging.DEBUG


from jhub_cas_authenticator.cas_auth import CASLocalAuthenticator

class CustomCASLocalAuthenticator(CASLocalAuthenticator):
    async def authenticate(self, handler, data):
        result = await super().authenticate(handler, data)
        return result

    async def validate_cas_ticket(self, ticket):
        response = await super().validate_cas_ticket(ticket)
        return response


c.JupyterHub.authenticator_class = CustomCASLocalAuthenticator

# debug-logging for testing
import logging
c.JupyterHub.log_level = logging.DEBUG


# CAS configuration
c.CASLocalAuthenticator.cas_login_url = 'https://<CAS server URL>/cas/'
c.CASLocalAuthenticator.cas_service_url = 'https://<jupyterhub URL>/login'
c.CASLocalAuthenticator.cas_client_ca_certs = '/srv/jupyterhub/CAS.pem'
c.CASLocalAuthenticator.cas_service_validate_url = 'https://<CAS server URL>/cas/p3/serviceValidate'


c.DockerSpawner.image_whitelist = {
    "Datasciences: python, R and Julia": "127.0.0.1:5000/datascience-notebook-capsule",
    "R and Spark Stack" : "quay.io/jupyter/all-spark-notebook",
    "Tensor Flow" : "quay.io/jupyter/tensorflow-notebook",
    "Julia" : "quay.io/jupyter/julia-notebook",
}

c.JupyterHub.services = [
    {
        "name": "jupyterhub-idle-culler-service",
        "command": [
            sys.executable,
            "-m", "jupyterhub_idle_culler",
             "--timeout=3000"
        ],
    }
]

c.JupyterHub.load_roles = [
    {
        "name": "jupyterhub-idle-culler-role",
        "scopes": [
            "list:users",
            "read:users:activity",
            "read:servers",
            "delete:servers",
        ],
        "services": ["jupyterhub-idle-culler-service"],
    }
]

Any help would be greatly appreciated

Is this only a problem with CASLocalAuthenticator, or do you see the problem with other authenticators? Can you turn on debug logging and share your logs?

1 Like