GafaelfawrAuthenticator

class rubin.nublado.authenticator.GafaelfawrAuthenticator(**kwargs)

Bases: Authenticator

JupyterHub authenticator using Gafaelfawr headers.

Rather than implement any authentication logic inside of JupyterHub, authentication is done via an auth_request handler made by the NGINX ingress controller. JupyterHub then only needs to read the authentication results from the headers of the incoming request.

Normally, the authentication flow for JupyterHub is to send the user to /hub/login and display a login form. The submitted values to the form are then passed to the authenticate method of the authenticator, which is responsible for returning authentication information for the user. That information is then stored in an authentication session and the user is redirected to whatever page they were trying to go to.

We however do not want to display an interactive form, since the authentication information is already present in the headers. We just need JupyterHub to read it.

The documented way to do this is to register a custom login handler on a new route not otherwise used by JupyterHub, and then enable the auto_login setting on the configured authenticator. This setting tells the built-in login page to, instead of presenting a login form, redirect the user to whatever URL is returned by login_url. In our case, this will be /hub/gafaelfawr/login. This simple handler will read the token from the header, retrieve its metadata, create the session and cookie, and then make the same redirect call the login form handler would normally have made after the authenticate method returned.

In this model, the authenticate method is not used, since the login handler never receives a form submission.

Notes

A possible alternative implementation that seems to be supported by the JupyterHub code would be to not override login_url, set auto_login, and then override get_authenticated_user in the authenticator to read authentication information directly from the request headers. It looks like an authenticator configured in that way would authenticate the user “in place” in the handler of whatever page the user first went to, without any redirects. This would be slightly more efficient and the code appears to handle it, but the current documentation (as of 1.5.0) explicitly says to not override get_authenticated_user.

This implementation therefore takes the well-documented path of a new handler and a redirect from the built-in login handler, on the theory that a few extra redirects is a small price to pay for staying within the supported and expected interface.

Parameters:

kwargs (Any)

Methods Summary

authenticate(handler, data)

Login form authenticator.

get_handlers(app)

Register the header-only login and the logout handlers.

login_url(base_url)

Override the login URL.

refresh_user(user[, handler])

Optionally refresh the user's token.

Methods Documentation

async authenticate(handler, data)

Login form authenticator.

This is not used in our authentication scheme.

Parameters:
Raises:

NotImplementedError – Raised if called.

Return type:

str | dict[str, Any] | None

get_handlers(app)

Register the header-only login and the logout handlers.

Parameters:

app (JupyterHub) – Tornado app in which to register the handlers.

Returns:

Additional routes to add.

Return type:

list of tuple

login_url(base_url)

Override the login URL.

This must be changed to something other than /login to trigger correct behavior when auto_login is set to true (as it is in our case).

Parameters:

base_url (str) – Base URL of this JupyterHub installation.

Returns:

URL to which the user is sent during login. For this authenticator, this is a URL provided by a login handler that looks at headers set by Gafaelfawr.

Return type:

str

async refresh_user(user, handler=None)

Optionally refresh the user’s token.

Parameters:
  • user (User) – JupyterHub user information.

  • handler (RequestHandler | None, default: None) – Tornado request handler.

Returns:

Returns True if we cannot refresh the auth state and should use the existing state. Otherwise, returns the new auth state taken from the request headers set by Gafaelfawr.

Return type:

bool or dict

Raises:

tornado.web.HTTPError – Raised with a 401 error if the username does not match our current auth state, since JupyterHub does not support changing users during refresh.