Opening singleuser instance in browser in JupyterHub API-only mode

Hello,

I’m trying to run JupyterHub in API-only mode. I got to the part where I start the user’s instance through API, however, when I try to navigate to the URL printed out by the Hub console, this is what happens:

image

This is the URL I get redirected to:

http://127.0.0.1:50585/hub/api/oauth2/authorize?client_id=jupyterhub-user-milos%2540milos.com&redirect_uri=%2Fuser%2Fmilos%40milos.com%2Foauth_callback&response_type=code&state=eyJ1dWlkIjogIjYyYTI2NzA0MmU2NTQzNmY5NmU0NmY5MjQxNTYzZmQwIiwgIm5leHRfdXJsIjogIi91c2VyL21pbG9zQG1pbG9zLmNvbS9sYWI_dG9rZW49In0

The environment I’m running the code in:

jupyter==1.0.0
jupyter_nbextensions_configurator==0.6.3
jupyterhub==4.0.2
notebook==6.5.4
configurable-http-proxy==0.3.0
jupyterlab==4.0.9

…with this JupyterHub config:

c = get_config()  # noqa

c.JupyterHub.allow_named_servers = True
c.JupyterHub.spawner_class = "simple"
c.JupyterHub.authenticator_class = "dummy"
c.JupyterHub.hub_routespec = "/hub/api"  # <-- API only mode
c.JupyterHub.services = [
    {
        "name": "my-service",
        "url": "http://localhost:8012/services/my-service",
        "admin": True,
        "api_token": "812a587d-8c23-4a60-873e-713c87084076",
    },
]

c.Authenticator.delete_invalid_users = True

And this is the console output when the local process spawner does it’s thing:

[I 2023-12-12 17:18:27.795 JupyterHub provider:659] Creating oauth client jupyterhub-user-milos%40milos.com
[I 2023-12-12 17:18:27.830 JupyterHub spawner:1689] Spawning jupyterhub-singleuser
[W 2023-12-12 17:18:28.863 ServerApp] A `_jupyter_server_extension_points` function was not found in jupyter_lsp. Instead, a `_jupyter_server_extension_paths` function was found and will be used for now. This function name will be deprecated in future releases of Jupyter Server.
[W 2023-12-12 17:18:29.397 ServerApp] A `_jupyter_server_extension_points` function was not found in nbclassic. Instead, a `_jupyter_server_extension_paths` function was found and will be used for now. This function name will be deprecated in future releases of Jupyter Server.
[W 2023-12-12 17:18:29.397 ServerApp] A `_jupyter_server_extension_points` function was not found in notebook_shim. Instead, a `_jupyter_server_extension_paths` function was found and will be used for now. This function name will be deprecated in future releases of Jupyter Server.
[I 2023-12-12 17:18:29.398 ServerApp] jupyter_lsp | extension was successfully linked.
[I 2023-12-12 17:18:29.401 ServerApp] jupyter_server_terminals | extension was successfully linked.
[I 2023-12-12 17:18:29.402 JupyterHubSingleUser] Starting jupyterhub single-user server extension version 4.0.2
[I 2023-12-12 17:18:29.402 JupyterHubSingleUser] Using default url from server extension lab: /lab
[I 2023-12-12 17:18:29.405 ServerApp] jupyterhub | extension was successfully linked.
[W 2023-12-12 17:18:29.406 LabApp] 'extra_template_paths' was found in both NotebookApp and ServerApp. This is likely a recent change. This config will only be set in NotebookApp. Please check if you should also config these traits in ServerApp for your purpose.
[I 2023-12-12 17:18:29.409 ServerApp] jupyterlab | extension was successfully linked.
[W 2023-12-12 17:18:29.411 NotebookApp] 'extra_template_paths' was found in both NotebookApp and ServerApp. This is likely a recent change. This config will only be set in NotebookApp. Please check if you should also config these traits in ServerApp for your purpose.
[I 2023-12-12 17:18:29.413 ServerApp] nbclassic | extension was successfully linked.
[I 2023-12-12 17:18:29.948 ServerApp] Extension package jupyter_nbextensions_configurator took 0.2808s to import
[W 2023-12-12 17:18:29.948 ServerApp] A `_jupyter_server_extension_points` function was not found in jupyter_nbextensions_configurator. Instead, a `_jupyter_server_extension_paths` function was found and will be used for now. This function name will be deprecated in future releases of Jupyter Server.
[I 2023-12-12 17:18:29.948 ServerApp] jupyter_nbextensions_configurator | extension was found and enabled by notebook_shim. Consider moving the extension to Jupyter Server's extension paths.
[I 2023-12-12 17:18:29.948 ServerApp] jupyter_nbextensions_configurator | extension was successfully linked.
[I 2023-12-12 17:18:29.949 ServerApp] notebook_shim | extension was successfully linked.
[I 2023-12-12 17:18:29.976 ServerApp] notebook_shim | extension was successfully loaded.
[I 2023-12-12 17:18:29.978 ServerApp] jupyter_lsp | extension was successfully loaded.
[I 2023-12-12 17:18:29.978 ServerApp] [jupyter_nbextensions_configurator] enabled 0.6.3
[I 2023-12-12 17:18:29.978 ServerApp] jupyter_nbextensions_configurator | extension was successfully loaded.
[I 2023-12-12 17:18:29.979 ServerApp] jupyter_server_terminals | extension was successfully loaded.
[I 2023-12-12 17:18:29.984 JupyterHub log:191] 200 GET /hub/api (@127.0.0.1) 1.27ms
[I 2023-12-12 17:18:29.985 JupyterHubSingleUser] Updating Hub with activity every 300 seconds
[I 2023-12-12 17:18:29.985 ServerApp] jupyterhub | extension was successfully loaded.
[I 2023-12-12 17:18:29.989 LabApp] JupyterLab extension loaded from /home/milos/.pyenv/versions/3.11.4/envs/myhub/lib/python3.11/site-packages/jupyterlab
[I 2023-12-12 17:18:29.989 LabApp] JupyterLab application directory is /home/milos/.pyenv/versions/3.11.4/envs/myhub/share/jupyter/lab
[I 2023-12-12 17:18:29.989 LabApp] Extension Manager is 'pypi'.
[I 2023-12-12 17:18:29.992 ServerApp] jupyterlab | extension was successfully loaded.

  _   _          _      _
 | | | |_ __  __| |__ _| |_ ___
 | |_| | '_ \/ _` / _` |  _/ -_)
  \___/| .__/\__,_\__,_|\__\___|
       |_|
                                                                           
Read the migration plan to Notebook 7 to learn about the new features and the actions to take if you are using extensions.

https://jupyter-notebook.readthedocs.io/en/latest/migrate_to_notebook7.html

Please note that updating to Notebook 7 might break some of your extensions.

[I 2023-12-12 17:18:29.997 ServerApp] nbclassic | extension was successfully loaded.
[I 2023-12-12 17:18:29.998 ServerApp] Serving notebooks from local directory: /tmp/milos@milos.com
[I 2023-12-12 17:18:29.998 ServerApp] Jupyter Server 2.12.1 is running at:
[I 2023-12-12 17:18:29.998 ServerApp] http://127.0.0.1:38941/user/milos@milos.com/lab?token=...
[I 2023-12-12 17:18:29.998 ServerApp]     http://127.0.0.1:38941/user/milos@milos.com/lab?token=...
[I 2023-12-12 17:18:29.998 ServerApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[I 2023-12-12 17:18:30.022 JupyterHub log:191] 200 POST /hub/api/users/milos@milos.com/activity (milos@milos.com@127.0.0.1) 19.51ms
[I 2023-12-12 17:18:30.385 ServerApp] Skipped non-installed server(s): bash-language-server, dockerfile-language-server-nodejs, javascript-typescript-langserver, jedi-language-server, julia-language-server, pyright, python-language-server, python-lsp-server, r-languageserver, sql-language-server, texlab, typescript-language-server, unified-language-server, vscode-css-languageserver-bin, vscode-html-languageserver-bin, vscode-json-languageserver-bin, yaml-language-server
[I 2023-12-12 17:18:31.559 ServerApp] 302 GET /user/milos@milos.com/ -> /user/milos@milos.com/lab? (@127.0.0.1) 0.50ms
[W 2023-12-12 17:18:31.559 JupyterHub _version:37] Single-user server has no version header, which means it is likely < 0.8. Expected 4.0.2
[I 2023-12-12 17:18:31.560 JupyterHub base:990] User milos@milos.com took 3.799 seconds to start
[I 2023-12-12 17:18:31.560 JupyterHub proxy:330] Adding user milos@milos.com to proxy /user/milos@milos.com/ => http://127.0.0.1:38941
[I 2023-12-12 17:18:31,563 configurable_http_proxy] Adding route /user/milos@milos.com -> http://127.0.0.1:38941
[I 2023-12-12 17:18:31,563 configurable_http_proxy] Route added /user/milos@milos.com -> http://127.0.0.1:38941
[I 2023-12-12 17:18:31,563 configurable_http_proxy] 201 POST /api/routes/user/milos@milos.com 
[I 2023-12-12 17:18:31.565 JupyterHub log:191] 201 POST /hub/api/users/milos%40milos.com/server (my-api@::1) 3819.75ms

I was hoping that someone could maybe tell me what am I doing wrong. Haven’t had much luck with docs and examples.

Also, regarding this part: JupyterHub and OAuth — JupyterHub documentation

Does this mean that the Hub API must remain a public API because of oauth2 in, e.g., k8s setup?

Yes, the hub api must be public in order for logging in to single-user servers via oauth to work. The JupyterHub OAuth handlers are under /hub/api/oauth for this reason.

Can you share the logs of the service the redirected to and the one that served http://127.0.0.1:50585/hub/api/oauth2/authorize?

Thanks for your reply!

Can you share the logs of the service the redirected to and the one that served http://127.0.0.1:50585/hub/api/oauth2/authorize?

Not really sure where to find these. However, i generated the token using the /users/:name/tokens route, and when I go to http://localhost:8000/user/milos@milos.com/lab?token={generated token}, I get this in the console:

[I 2023-12-19 09:59:54,466 configurable_http_proxy] 200 GET /api/routes 
[I 2023-12-19 09:59:54.601 JupyterHub log:191] 200 GET /hub/api/user (milos@milos.com@127.0.0.1) 17.08ms
[W 2023-12-19 09:59:54.622 ServerApp] 404 GET /user/milos%40milos.com/lab?token=[secret] (milos@milos.com@127.0.0.1) 43.12ms
[W 2023-12-19 09:59:54,623 tornado.access] 404 GET /user/milos@milos.com/lab?token=ab8832a61ced4136a84ad08a8425d900 (127.0.0.1) 49.78ms
[W 2023-12-19 09:59:54.663 ServerApp] 404 GET /user/milos%40milos.com/static/style/bootstrap.min.css?v=0e8a7fbd6de23ad6b27ab95802a0a0915af6693af612bc304d83af445529ce5d95842309ca3405d10f538d45c8a3a261b8cff78b4bd512dd9effb4109a71d0ab (@127.0.0.1) 1.42ms
[W 2023-12-19 09:59:54,664 tornado.access] 404 GET /user/milos@milos.com/static/style/bootstrap.min.css?v=0e8a7fbd6de23ad6b27ab95802a0a0915af6693af612bc304d83af445529ce5d95842309ca3405d10f538d45c8a3a261b8cff78b4bd512dd9effb4109a71d0ab (127.0.0.1) 4.47ms
[W 2023-12-19 09:59:54.666 ServerApp] 404 GET /user/milos%40milos.com/static/style/bootstrap-theme.min.css?v=8b2f045cb5b4d5ad346f6e816aa2566829a4f5f2783ec31d80d46a57de8ac0c3d21fe6e53bcd8e1f38ac17fcd06d12088bc9b43e23b5d1da52d10c6b717b22b3 (@127.0.0.1) 2.07ms
[W 2023-12-19 09:59:54,667 tornado.access] 404 GET /user/milos@milos.com/static/style/bootstrap-theme.min.css?v=8b2f045cb5b4d5ad346f6e816aa2566829a4f5f2783ec31d80d46a57de8ac0c3d21fe6e53bcd8e1f38ac17fcd06d12088bc9b43e23b5d1da52d10c6b717b22b3 (127.0.0.1) 6.48ms
[W 2023-12-19 09:59:54.667 ServerApp] 404 GET /user/milos%40milos.com/static/style/index.css?v=30372e3246a801d662cf9e3f9dd656fa192eebde9054a2282449fe43919de9f0ee9b745d7eb49d3b0a5e56357912cc7d776390eddcab9dac85b77bdb17b4bdae (@127.0.0.1) 2.38ms
[W 2023-12-19 09:59:54,668 tornado.access] 404 GET /user/milos@milos.com/static/style/index.css?v=30372e3246a801d662cf9e3f9dd656fa192eebde9054a2282449fe43919de9f0ee9b745d7eb49d3b0a5e56357912cc7d776390eddcab9dac85b77bdb17b4bdae (127.0.0.1) 6.63ms
[W 2023-12-19 09:59:54.671 ServerApp] 404 GET /user/milos%40milos.com/static/logo/logo.png?v=a2a176ee3cee251ffddf5fa21fe8e43727a9e5f87a06f9c91ad7b776d9e9d3d5e0159c16cc188a3965e00375fb4bc336c16067c688f5040c0c2d4bfdb852a9e4 (@127.0.0.1) 1.25ms
[W 2023-12-19 09:59:54,671 tornado.access] 404 GET /user/milos@milos.com/static/logo/logo.png?v=a2a176ee3cee251ffddf5fa21fe8e43727a9e5f87a06f9c91ad7b776d9e9d3d5e0159c16cc188a3965e00375fb4bc336c16067c688f5040c0c2d4bfdb852a9e4 (127.0.0.1) 3.17ms
[W 2023-12-19 09:59:54.689 ServerApp] 404 GET /user/milos%40milos.com/static/favicon.ico?v=50afa725b5de8b00030139d09b38620224d4e7dba47c07ef0e86d4643f30c9bfe6bb7e1a4a1c561aa32834480909a4b6fe7cd1e17f7159330b6b5914bf45a880 (@127.0.0.1) 1.65ms
[W 2023-12-19 09:59:54,689 tornado.access] 404 GET /user/milos@milos.com/static/favicon.ico?v=50afa725b5de8b00030139d09b38620224d4e7dba47c07ef0e86d4643f30c9bfe6bb7e1a4a1c561aa32834480909a4b6fe7cd1e17f7159330b6b5914bf45a880 (127.0.0.1) 3.72ms

And this screen:

image

However, if I do the same procedure for the user that exists as a Linux user, everything works fine.

I guess the only problem is for the users I created through API which do not exist as Linux users.

EDIT:

In the scenario where the Linux user exists, however, after being successfully redirected to Lab page, I cannot refresh the page anymore. It goes from this:

image

to this:

This is the URL it tries to go to:

http://localhost:8000/hub/login?next=%2Fhub%2Fapi%2Foauth2%2Fauthorize%3Fclient_id%3Djupyterhub-user-milos%26redirect_uri%3D%252Fuser%252Fmilos%252Foauth_callback%26response_type%3Dcode%26state%3DeyJ1dWlkIjogIjJjYjAyOTIwMzM0OTQ3Njg4ZTU5MDMyZDRhYTA3YTExIiwgIm5leHRfdXJsIjogIi91c2VyL21pbG9zL2xhYiIsICJjb29raWVfbmFtZSI6ICJqdXB5dGVyaHViLXVzZXItbWlsb3Mtb2F1dGgtc3RhdGUtYVBrbFhsRUgifQ