Authenticators#

Quetz delegates the task of authenticating users (checking passwords etc.) to third-party identity providers. It can communicate via the OAuth2 and OpenID protocols supported by services such as Github and Google. This means that you can configure Quetz to have users log in with their Github accounts. Quetz also supports the PAM-based authentication which uses local Unix users for authentication.

Warning

While it is possible to register and use multiple authenticators at once, it is heavily discouraged and a warning will be printed. Currently quetz does not automatically merge accounts based on email addresses and usernames from different auth providers can overlap.

A warning will be printed when running quetz with multiple activated auth providers.

Built-in authenticators#

These authenticator classes are built-in and can be activated by adding relevant section to the configuration file. See below for more details (the class names are only for reference, they are already included in the Quetz server).

PAM#

class quetz.authentication.pam.PAMAuthenticator(config: Config, provider=None, app=None)#

Use PAM to authenticate with local system users.

To enable add the following to your configuration file:

[pamauthenticator]
# name for the provider, used in the login URL
provider = "pam"
# use the following to translate the Unix groups that
# users might belong to user role on Quetz server
admin_groups = ["root", "quetz"]
maintainer_groups = []
user_groups = []

On most Linux systems you can add users with:

useradd USERNAME
# set password interactively with
passwd USERNAME

Note

For this authenticator to work, the user who runs the server must be root or be in shadow group.

Github#

class quetz.authentication.github.GithubAuthenticator(config: Config, client_kwargs=None, provider=None, app=None)#

Use Github account to authenticate users with Quetz.

To enable add the following to the configuration file:

[github]
client_id = "fde330aef1fbe39991"
client_secret = "03728444a12abff17e9444fd231b4379d58f0b"

You can obtain client_id and client_secret by registering your application with Github at this URL: settings/applications.

Gitlab#

class quetz.authentication.gitlab.GitlabAuthenticator(config: Config, client_kwargs=None, provider=None, app=None)#

Use Gitlab account to authenticate users with Quetz.

To enable add the following to the configuration file:

[gitlab]
client_id = "fde330aef1fbe39991"
client_secret = "03728444a12abff17e9444fd231b4379d58f0b"

The above will use gitlab. You can specify a self-hosted GitLab instance using the url parameter:

[gitlab]
url = "https://gitlab.mydomain.org"
client_id = "fde330aef1fbe39991"
client_secret = "03728444a12abff17e9444fd231b4379d58f0b"

You can obtain client_id and client_secret by registering your application with Gitlab at this URL: -/profile/applications or https://gitlab.mydomain.org/admin/applications if using a self-hosted GitLab instance. Select openid as scope. If you want to collect email addresses, make sure to also select email, profile and read_user as scope in the Gitlab interface.

Google#

class quetz.authentication.google.GoogleAuthenticator(config: Config, client_kwargs=None, provider=None, app=None)#

Use Google account to authenticate users with Quetz.

To enable add the following to the configuration file:

[google]
client_id = "1111111111-dha39auqzp92110sdf.apps.googleusercontent.com"
client_secret = "03728444a12abff17e9444fd231b4379d58f0b"

You can obtain client_id and client_secret by registering your application with Google platfrom at this URL: https://console.developers.google.com/apis/credentials.

Jupyterhub#

class quetz.authentication.jupyterhub.JupyterhubAuthenticator(config: Config, client_kwargs=None, provider=None, app=None)#

Use Oauth2 protcol to authenticate with jupyterhub server, which acts as identity provider.

To activate add the following section to the config.toml (see Config file):

[jupyterhubauthenticator]

# client credentials, they need to be registered with
# jupyterhub by adding an external service
client_id = "quetz_client"
client_secret = "super-secret"

# token enpoint of Jupyterhub, needs to be accessible from Quetz server
access_token_url = "http://JUPYTERHUB_HOST:PORT/hub/api/oauth2/token"

# authorize endpoint of JupyterHub, needs to be accessible from users' browser
authorize_url = "http://JUPYTERHUB_HOST:PORT/hub/api/oauth2/authorize"

# API root, needs to be accesible from Quetz server
api_base_url = "http://JUPYTERHUB_HOST:PORT/hub/api/"

To configure quetz as an oauth client in JupyterHub, you will need to define a JupyterHub service. You can achieve it by adding the following to the jupyterhub_config.py file of your JupyterHub instance:

c.JupyterHub.services = [
    {
        # service name, it will be used to setup routers
        'name': 'quetz',
        # quetz URL to setup redirections, only required if you use
        # JupyterHub url scheme
        'url': 'http://QUETZ_HOST:PORT',
        # any secret >8 characters, you will also need to set
        # the client_secret in the authenticator config with this
        # string
        'api_token': 'super-secret',
        # client_id in the authenticator config
        'oauth_client_id': 'quetz_client',
        # URL of the callback endpoint on the quetz server
        'oauth_redirect_uri': 'http://QUETZ_HOST:PORT/auth/jupyterhub/authorize',
    }
]

Custom authenticators#

You can also implement new authenticators for Quetz server.

Authentication base classes#

The authenticator should derive from one of the base classes:

class quetz.authentication.base.BaseAuthenticator(config: Config, provider=None, app=None)#

Base class for authenticators.

Subclasses MUST implement:

Subclasses SHOULD implement:

  • configure()

  • validate_token()

class quetz.authentication.base.SimpleAuthenticator(config: Config, provider=None, app=None)#

A demo of a possible implementation. It redirects users to a simple HTML form where they can type their username and password.

Note: Consider this an example. In your production setting you would probably want to redirect to your custom login page. Make sure that this page submits data to /auth/{provider}/authorize endpoint.

class quetz.authentication.oauth2.OAuthAuthenticator(config: Config, client_kwargs=None, provider=None, app=None)#

Base class for authenticators using Oauth2 protocol and its variants.

The authenticate() method is already implemented, but you will need to override some of the following variables in sublasses to make it work:

Variables:
  • provider (str) – name of the provider (it will be used in the url)

  • handler_cls – class with handlers for all oauth2 relevant endpoints in Quetz server

  • client_id – required, client id registered with the provider

  • client_secret – required, likewise

  • is_enabled (bool) – True if authenticator is enabled, can be configured in configure() method

  • access_token_url – URL of the OAuth2 endpoint ot request a token

  • authorize_url – URL of the OAuth2 authorize endpoint

  • api_base_url – URL of the API root of the provider server

  • validate_token_url – path of endpoint to validate the token

Implement authentication logic#

To implement some custom authentication logic, your class should override at least authenticate() method (except for OAuthAuthenticator subclasses):

async BaseAuthenticator.authenticate(request, data=None, dao=None, config=None, **kwargs) Optional[Union[str, UserDict]]#

Authentication user with the data submitted.

This method should return:

  • None if the authentication failed,

  • string with username for successful authentication

  • or a dictionary with keys username, profile (user profile data), auth_state (extra authentication state to be stored in the browser session).

For example, the custom authenticator might be:

class DictionaryAuthenticator(SimpleAuthenticator):
    """Simple demo authenticator that authenticates with
    users from a dictionary of usernames and passwords."""

    passwords: dict = {"happyuser": "happy"}
    provider = "dict"

    async def authenticate(self, request, data, **kwargs):
        """``data`` argument is username and password entered by
        user in the login form."""

        if self.passwords.get(data['username']) == data['password']:
            return data['username']

Registering authenticator#

The standard way to register an authenticator with Quetz, is to distibute it as a plugin (see Plugins). To automatize the creation of a plugin, check out our cookiecutter template.

If not using the cookiecutter, you can register an authenticator with Quetz by defining an entry point. You can create entry point with the following snippet in the setup.py:

from setuptools import setup

# you will need to adapt these variables
# to the names from your package
PACKAGE_NAME = quetz_dictauthenticator
AUTHENTICATOR_CLASS = "DictAuthenticator"
MODULE_NAME = authenticators

setup(
    name="quetz-dictauthenticator",
    install_requires="quetz",
    entry_points={
        "quetz.authenticator": [
            f"{AUTHENTICATOR_CLASS.lower()} = {PACKAGE_NAME}:{MODULE_NAME}.{AUTHENTICATOR_CLASS}"
        ]
    },
    packages=[PACKAGE_NAME],
)