Auth_state from authenticator always null

Hi,

we are using Keycloak for auth and GenericAuthenticator. The auth_state is enabled, for auth and global, and JUPYTERHUB_CRYPT_KEY is set. It all starts correct and auth works.

We are trying to get the access token and refresh token inside notebook.
But auth state is null if I use

if auth_info:
    print("\nAuth Info:")
    print(json.dumps(auth_info, indent=2))
    
    print("\nOAuth Details:")
    print(f"Client ID: {os.environ.get('JUPYTERHUB_CLIENT_ID')}")
    print(f"OAuth Scopes: {os.environ.get('JUPYTERHUB_OAUTH_SCOPES')}")

I even tried custom authenticator

from oauthenticator.generic import GenericOAuthenticator

class KeycloakAuthenticator(GenericOAuthenticator):
    enable_auth_state = True

    async def authenticate(self, handler, data=None):
        userdict = await super().authenticate(handler, data)
        if userdict:
            auth_state = {
                'access_token': userdict.get('access_token'),
                'refresh_token': userdict.get('refresh_token'),
                'token_response': userdict.get('token_response', {}),
                'oauth_user': userdict.get('oauth_user', {})
            }
            userdict['auth_state'] = auth_state
        return userdict

    async def pre_spawn_start(self, user, spawner):
        auth_state = await user.get_auth_state()
        if not auth_state:
            return
        spawner.environment['OAUTH_ACCESS_TOKEN'] = auth_state['access_token']
        if 'refresh_token' in auth_state:
            spawner.environment['OAUTH_REFRESH_TOKEN'] = auth_state['refresh_token']

I configure in config this custom auth, it works fine, but the auth_state still does not seem to work.

But even that does not return auth_state, it is null. How can I make it so that the token can be accessed in the notebook. The API we have to call from notebook will use same token since it is same auth.

Am I missing something or doing something wrong?

Are you using the latest versions of JupyterHub and OAuthenticator?

Can you add some logging to your custom authenticator to output userdict after every line (when sharing the output redact any secrets)?

We are using JupyterHub 5.2.1 20241112132428

How do i check OAuthenticator? Does not seem to be in the list of libs:

 sudo -E /opt/tljh/user/bin/pip list

Package                   Version
------------------------- --------------
alembic                   1.14.0
annotated-types           0.7.0
anyio                     4.6.2.post1
archspec                  0.2.3
argon2-cffi               23.1.0
argon2-cffi-bindings      21.2.0
arrow                     1.3.0
asttokens                 2.4.1
async-lru                 2.0.4
attrs                     24.2.0
babel                     2.16.0
bar                       0.2.1
beautifulsoup4            4.12.3
bleach                    6.2.0
boltons                   24.0.0
Brotli                    1.1.0
certifi                   2024.8.30
certipy                   0.2.1
cffi                      1.17.1
charset-normalizer        3.3.2
colorama                  0.4.6
comm                      0.2.2
conda                     24.7.1
conda-libmamba-solver     24.7.0
conda-package-handling    2.3.0
conda_package_streaming   0.10.0
contourpy                 1.3.0
cryptography              43.0.3
cycler                    0.12.1
debugpy                   1.8.7
decorator                 5.1.1
defusedxml                0.7.1
distro                    1.9.0
executing                 2.1.0
fastjsonschema            2.20.0
fonttools                 4.54.1
fqdn                      1.5.1
frozendict                2.4.4
greenlet                  3.1.1
h11                       0.14.0
h2                        4.1.0
hpack                     4.0.0
httpcore                  1.0.6
httpx                     0.27.2
hyperframe                6.0.1
idna                      3.10
ipykernel                 6.29.5
ipython                   8.29.0
ipywidgets                8.1.5
isoduration               20.11.0
jedi                      0.19.1
Jinja2                    3.1.4
json5                     0.9.25
jsonpatch                 1.33
jsonpointer               3.0.0
jsonschema                4.23.0
jsonschema-specifications 2024.10.1
jupyter_client            8.6.3
jupyter_core              5.7.2
jupyter-events            0.10.0
jupyter-lsp               2.2.5
jupyter-resource-usage    1.1.0
jupyter_server            2.14.2
jupyter_server_terminals  0.5.3
jupyterhub                5.2.1
jupyterlab                4.2.5
jupyterlab_pygments       0.3.0
jupyterlab_server         2.27.3
jupyterlab_widgets        3.0.13
kiwisolver                1.4.7
libmambapy                1.5.9
Mako                      1.3.6
mamba                     1.5.9
MarkupSafe                3.0.2
matplotlib                3.9.2
matplotlib-inline         0.1.7
menuinst                  2.1.2
mistune                   3.0.2
nbclient                  0.10.0
nbconvert                 7.16.4
nbformat                  5.10.4
nbgitpuller               1.2.1
nest-asyncio              1.6.0
notebook                  7.2.2
notebook_shim             0.2.4
numpy                     2.1.3
oauthlib                  3.2.2
overrides                 7.7.0
packaging                 24.1
pamela                    1.2.0
pandocfilters             1.5.1
parso                     0.8.4
pexpect                   4.9.0
pillow                    11.0.0
pip                       24.2
platformdirs              4.3.6
pluggy                    1.5.0
prometheus_client         0.21.0
prompt_toolkit            3.0.48
psutil                    5.9.8
ptyprocess                0.7.0
pure_eval                 0.2.3
pycosat                   0.6.6
pycparser                 2.22
pydantic                  2.9.2
pydantic_core             2.23.4
Pygments                  2.18.0
pyparsing                 3.2.0
PySocks                   1.7.1
python-dateutil           2.9.0.post0
python-json-logger        2.0.7
PyYAML                    6.0.2
pyzmq                     26.2.0
referencing               0.35.1
requests                  2.32.3
rfc3339-validator         0.1.4
rfc3986-validator         0.1.1
rpds-py                   0.20.1
ruamel.yaml               0.18.6
ruamel.yaml.clib          0.2.8
Send2Trash                1.8.3
setuptools                74.1.2
six                       1.16.0
sniffio                   1.3.1
soupsieve                 2.6
SQLAlchemy                2.0.36
stack-data                0.6.3
terminado                 0.18.1
there                     0.0.12
tinycss2                  1.4.0
tornado                   6.4.1
tqdm                      4.66.5
traitlets                 5.14.3
truststore                0.9.2
types-python-dateutil     2.9.0.20241003
typing_extensions         4.12.2
uri-template              1.3.0
urllib3                   2.2.3
wcwidth                   0.2.13
webcolors                 24.8.0
webencodings              0.5.1
websocket-client          1.8.0
wheel                     0.44.0
widgetsnbextension        4.0.13
zstandard                 0.23.0

All the token seem to be correct in authenticator:

2024-11-12 14:27:15 - === Starting authentication process ===
2024-11-12 14:27:15 - Input data: null
2024-11-12 14:27:15 - After super().authenticate, full userdict: {
  "name": "test@test.com",
  "admin": true,
  "auth_state": {
    "access_token": "<REDACTED>",
    "refresh_token": "<REDACTED>",
	"id_token": "<REDACTED>",
    "scope": [
      "openid",
      "email",
      "profile"
    ],
    "token_response": {
	  "access_token": "<REDACTED>",
      "expires_in": 300,
      "refresh_expires_in": 2574986,
	  "refresh_token": "<REDACTED>",
	  "token_type": "Bearer",
	  "id_token": "<REDACTED>",
      "not-before-policy": 0,
      "session_state": "<REDACTED>",
      "scope": "openid email profile"
    },
    "oauth_user": {
      "sub": "<REDACTED>",
      "email_verified": true,
      "preferred_username": "admin",
      "given_name": "",
      "family_name": "",
      "email": "test@test.com"
    }
  }
}
2024-11-12 14:27:15 - Creating auth_state...
2024-11-12 14:27:15 - Created auth_state: {
  "access_token": null,
  "refresh_token": null,
  "token_response": {},
  "oauth_user": {}
}
2024-11-12 14:27:15 - Final userdict with auth_state: {
  "name": "test@test.com",
  "admin": true,
  "auth_state": {
    "access_token": null,
    "refresh_token": null,
    "token_response": {},
    "oauth_user": {}
  }
}
2024-11-12 14:27:15 - === Authentication process completed ===

Edit: there was an error in code before:

existing_auth_state = userdict.get('auth_state', {})

 auth_state = {
                'access_token': existing_auth_state.get('access_token'),
                'refresh_token': existing_auth_state.get('refresh_token'),
                'token_response': existing_auth_state.get('token_response', {}),
                'oauth_user': existing_auth_state.get('oauth_user', {})
            }

Now in all states and processes the tokens are correct in log but in notebook it is still not available

Ok, it works now. After restarting everything the tokens are available. The issue was in authenticator that I was get keys in wrong level.

My problem was fixed by

existing_auth_state = userdict.get('auth_state', {})

 auth_state = {
                'access_token': existing_auth_state.get('access_token'),
                'refresh_token': existing_auth_state.get('refresh_token'),
                'token_response': existing_auth_state.get('token_response', {}),
                'oauth_user': existing_auth_state.get('oauth_user', {})
            }