Saturday, June 1, 2019

JupyterHub OAuth via setting scopes

Recently I set up a JupyterHub instance, a system for cloud-hosting Jupyter notebooks. JupyterHub supports authentication by a number of different mechanisms. As the code for the notebook is hosted on GitLab,, I set up OAuth to GitLab as the main authentication mechanism.

Gitlab supports a number of scopes to limit what the granted OAuth token is allowed to do:

apiGrants complete read/write access to the API, including all groups and projects.
read_userGrants read-only access to the authenticated user's profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users.
read_repositoryGrants read-only access to repositories on private projects using Git-over-HTTP (not using the API).
write_repositoryGrants read-write access to repositories on private projects using Git-over-HTTP (not using the API).
read_registryGrants read-only access to container registry images on private projects.
sudoGrants permission to perform API actions as any user in the system, when authenticated as an admin user.
openidGrants permission to authenticate with GitLab using OpenID Connect. Also gives read-only access to the user's profile and group memberships.
profileGrants read-only access to the user's profile data using OpenID Connect.
emailGrants read-only access to the user's primary email address using OpenID Connect.

However I found that if I didn't grant api permissions on the gitlab side, the authentication would always fail with "The requested scope is invalid, unknown, or malformed." It appears that the JupyterHub OAuth client was not requesting any specific scope, and that defaults to "api" — which is far too powerful a permission to grant for this purpose, as it allows read/write access to everything when all we really need to know is that the user exists.

Setting the OAuth scope for the JupyterHub client to request turns out to be quite simple to do in the configuration, albeit not documented:

  c.GitLabOAuthenticator.scope = ['read_user']

A pull request to add documentation on this for GitLabOAuthenticator has been submitted.