Couldn't authenticate WebSocket connection with NullAuthenticator

Hello,

Background

I’m running into an issue with my Jupyter Notebook when using the NullAuthenticator. I’m leveraging the JupyterHub API to start a server and generate a token for the user. I then navigate to the spawned server’s endpoint using the token http://localhost:8000/user/<username>/?token=<token>. The notebook is visible and seems to work as expected, however attempting to execute a cell generates the error in the console shown below.

WebSocket connection to 'ws://localhost:8000/user/<username>/api/kernels/04218e9c-183c-471a-96ef-02cf3d770e9b/channels?session_id=8eccf31d-173c-496e-ae48-2565bd2a9f49' failed: Error during WebSocket handshake: Unexpected response code: 403

Checking the logs of the notebook instance (running in a container via DockerSpawner) I see that I get the corresponding error.

[W 2023-07-19 16:32:48.175 ServerApp] 403 GET /user/<username>/api/kernels/04218e9c-183c-471a-96ef-02cf3d770e9b/channels?session_id=8eccf31d-173c-496e-ae48-2565bd2a9f49 (@::ffff:172.21.0.1) 1.41m

If I change the autheticator from null to dummy and don’t provide the token in the URL, then I can run the cells, so I assume it is how I’m generating/configuring the token.

Question

What am I missing that is blocking the websocket from being established?

Supporting Content

JupyterHub Config

c = get_config()

## Network Settings
# we need the hub to listen on all ips when it is in a container
c.JupyterHub.hub_ip = '0.0.0.0'
# the hostname/ip that should be used to connect to the hub
# this is usually the hub container's name
c.JupyterHub.hub_connect_ip = 'jupyterhub'

## Authenticator Setting
# Use null authenticator so login is only possible through API
c.JupyterHub.authenticator_class = 'null'

## Spawner Settings
# Use custom spawner
c.JupyterHub.spawner_class = 'filespawner.FileSpawner'

# Network used to communicate with user containers
c.DockerSpawner.network_name = 'jupyterhub'

# Delete containers when they are stopped
c.DockerSpawner.remove = True

## API Settings
# Create the NIST service
c.JupyterHub.services = [
    { 'name': 'service-custom', 'api_token': '<my token>' }
]

# Grant NIST service account admin privledges
c.JupyterHub.load_roles = [
    {
        'name': 'service-role',
        'scopes': [
            # Ability to control users
            'admin:users',
            # Ability to start/stop/delete servers
            'admin:servers',
            # Ability to make tokens
            'tokens',
            # See current users
            'list:users'
        ],
        'services': [
            # assign the service the above permissions
            'service-custom'
        ],
    }
]

Code Snippet for Token Generation

async function requestToken(user) {
  const response = await fetch(`http://localhost:8000/hub/api/users/${user}/tokens`, {
    method: 'POST',
    headers: AUTH_HEADER
  });

  console.log(await response.text());
}

Hi

Did you find any solution for it?

I am having the same problem

I suspect you are hitting this issue. The workaround is to set:

JUPYTERHUB_SINGLEUSER_EXTENSION=0

in the single-user environment to opt-out of the singleuser extension implementation while waiting for a fix.