Accessing upstream oauth2 token from python code within singleuser notebook server

Hello together,

I’m using and enjoying jupyter notebooks since several years, but only in single user environments.

Now I’m in an use case where I need JupyterHub to spawn user centric instances.
Following you’re guides I managed to use KeyCloak as authentication provider for login, which is quite nice.

What I don’t understand is the following, shown in the graphic:

My business application and JupyterHub share the same authentication provider (KeyCloak), so once I logged in to one of them, I can directly switch to the other one without being forced to login again.

I’ve seen the documentation of pre_spawn_start to forward a bearer token to the singleuser server:
https://jupyterhub.readthedocs.io/en/stable/reference/authenticators.html#using-auth-state

That is nice to initially forward the information, but when the token (and refresh token) expires, because I’m just using data and performing calculations based on the data without interacting with my application backend or keycloak directly, there is no direct way to refresh my token.

The clients for OpenID Connect / OAuth2 I’ve found all require a client_secret or a web interaction to authenticate. Nothing I would do from within my python scripts.

QUESTION:
What I’m searching for is a possibility to make JupyterHub/public-proxy/api refreshing my auth_token from keycloak as soon as it expires. So that my python scripts within the singleuser notebook server can fetch a valid token from the api to communicate with the application backend.

The idea of forcing my users to reauthenticate against keycloak before communicating with the application backend, while they are still using jupyterhub (which depends on keycloak auth), but do not directly communicate with the application backend which would ideally refresh the token, is something I’m trying to omit.

Thanks in advance for an hint,
Alex

1 Like

It looks like someone’s written a dedicated KeyCloakAuthenticator that implements refresh_user to refresh the token (note I haven’t used this authenticator so can’t vouch for it):

Unfortunately there isn’t a way for a user to fetch the updated auth_state, though this is under discussion:

Thanks for the reply. I’ll have a look at both :slight_smile:

Especially the auth state thing is what I’ve been searching for.

1 Like

@warwing this is an interesting use-case - we had a similar need in the renku project (https://renkulab.io) to use the oauth2 credentials to communicate with GitLab from the user jupyter sessions. One problem we encountered was that having access to the oauth2 token in the sessions has huge security implications (users being given code by someone else to execute in their sessions) - we handled it by creating a proxy sidecar container that handles the credentials for the user (and limits where those credentials can be used). Not sure if this applies to you but thought I’d mention it!

1 Like

@rokroskar Very good point, which I have completely overseen. Thanks for that!
From a conceptional point, my view on JupyterHub was, that a user has his own workspace, where nobody else is allowed to look at.

As long as the “admin_users” is disabled, this should be true (no issue) - right?

So enabling “admin_users” is an important thing, that the owner of the installed system has to decide.

What I don’t understand is, how a sidecar container may help. Because when the python scripts inside the jupyter notebooks have the permission (during runtime access to the auth token) to execute an operation on the application backend and the admin acts as the regular user - what would make a difference??

right, in our case it was simply that the use of the token is restricted to a specific repository - the proxy only adds the token to requests to a specific URL. If you have the oauth token readily available in the session, it can be used to make requests to any API that accepts it. So in our case, the use of the token is completely restricted to pushing/pulling from a single repository, and it is never visible to the user.