Custom Authenticators

Let’s peek at the Authenticator classes:

In [1]:
from jupyterhub.auth import Authenticator, PAMAuthenticator
In [2]:
Authenticator.authenticate?
Signature: Authenticator.authenticate(self, handler, data)
Docstring:
Authenticate a user with login form data

This must be a tornado gen.coroutine.
It must return the username on successful authentication,
and return None on failed authentication.

Checking the whitelist is handled separately by the caller.

Args:
    handler (tornado.web.RequestHandler): the current request handler
    data (dict): The formdata of the login form.
                 The default form has 'username' and 'password' fields.
Returns:
    username (str or None): The username of the authenticated user,
    or None if Authentication failed
File:      ~/dev/jpy/jupyterhub/jupyterhub/auth.py
Type:      function

PAM calls out to a library with the given username and password:

In [3]:
PAMAuthenticator.authenticate??
Signature: PAMAuthenticator.authenticate(self, handler, data)
Source:
    @gen.coroutine
    def authenticate(self, handler, data):
        """Authenticate with PAM, and return the username if login is successful.

        Return None otherwise.
        """
        username = data['username']
        try:
            pamela.authenticate(username, data['password'], service=self.service)
        except pamela.PAMError as e:
            if handler is not None:
                self.log.warning("PAM Authentication failed (%s@%s): %s", username, handler.request.remote_ip, e)
            else:
                self.log.warning("PAM Authentication failed: %s", e)
        else:
            return username
File:      ~/dev/jpy/jupyterhub/jupyterhub/auth.py
Type:      function

Here’s a super advanced Authenticator that does very secure password verification:

In [4]:
class SuperSecureAuthenticator(Authenticator):
    def authenticate(self, handler, data):
        username = data['username']
        # check password:
        if data['username'] == data['password']:
            return username

Exercise:

Write a custom username+password Authenticator where:

  1. passwords are loaded from a dict
  2. hashed+salted passwords are stored somewhere, but not cleartext passwords
In [5]:
# possibly useful:

from jupyterhub.utils import hash_token, compare_token
hash_token('mypassword')
Out[5]:
'sha512:16384:98400e241da5a64d:6e2c468dea2e6ec6f185936f6ac1a96e6046c4cdf6c0156aecb03fcd2e9963c5058cbc7cf9d7792ed9edc5bc1703f6e14274b7ba5804781499e49bb077aacccb'
In [6]:
compare_token(_, 'mypassword')
Out[6]:
True