Simpler authentication for small scale JupyterHubs with NativeAuthenticator

NativeAuthenticator adds traditional username / password based sign up, login, & user management features to JupyterHub

Leticia Portella
Jupyter Blog

--

In this post I'll tell you about the new JupyterHub authenticator I implemented in the last couple of months, not only technically but also the context in which it was built. If you have no idea what JupyterHub is and how it works, I recommend this short talk by the awesome Carol Willing 🙃.

But first things first, let me introduce myself! My name is Leticia and I'm Brazilian 🇧🇷. Last year (2018) I saw an opening for an internship program called Outreachy. I am a woman, a self-taught programmer with a non traditional background (I'm an oceanographer) and I had no job at this time. The program would fit me perfectly. Lucky me, Jupyter was one of the projects available! Thus, everything you'll see here is the result (so far) of everything I've been doing with the internship and the help of my mentors.

Before going any further I would also like to thank the Berkeley Institute for Data Science and NumFocus for jointly sponsoring my Outreachy internship. This was a huge opportunity for me ❤️

Why do we need another authenticator?

JupyterHub authenticators determine how users on a particular installation of JupyterHub can log in. For example, here are a few common authenticators already available to JupyterHub:

  • PAM Authenticator: Any whitelisted user with an account and password on the system will be allowed to login;
  • OAuthenticator: An authenticator that uses the login of other services (OAuth) to authenticate on the Jupyterhub (such as Azure, Github or Moodle);
  • First Use Authenticator: A new user is created when the user logs in for the first time.

Large installations usually rely on a third party service (using OAuthenticator), while smaller ones maintain user accounts by hand (PAM Authenticator). However, maintining user lists by hand can be a lot of effort, especially as the number of users grows. Native Authenticator was created to supply smaller JupyterHub installations with a more convenient authentication system instead of maintaining user accounts by hand, without the overhead of needing an external third party service.

Native Authenticator features

Native Authenticator was added to PyPI this week and we are super excited about this! The default features available on the authenticator are:

  • A signup page that creates new users
  • Username sanitation (avoiding spaces, commas and backslashes)
  • “Administrator” privileges that can be given to specific users
  • The ability for users to change their passwords
  • A panel for controlling access to the system on a user-by-user basis

Some optional features are available and can be activated through the configuration file and will be discussed further on this post 🙃

Installing and using Native Authenticator

To use the authenticator, you can install it through pip :

$ pip install jupyterhub-nativeauthenticator

You can configure JupyterHub to work with Native Authenticator by adding the following line to the configuration file:

c.JupyterHub.authenticator_class = 'nativeauthenticator.NativeAuthenticator'

and that is it 😉

Default authenticator workflow

Once you’ve configured your JupyterHub installation to work with Native Authenticator, no new user can enter the system. To enter the system, a new user must sign up. To do so, the operator of the Hub should point users to/hub/signup and create a new username and password. After submitting the information, the user receives a message indicating that the information was sent to an admin.

By default, all users that Sign Up are unauthorized and can't login to the system. The admin will need to enter the Authorization Area dashboard at /hub/authorize and authorize the users into the system. The panel looks like this:

Authorized users are shown with a green background while unauthorized users have a white background. Each has a button to change the authorization status. Once a user is authorized, it will be able to go to the / endpoint and login.

The default workflow of the Native Authenticator is like this:

Default workflow of Native Authenticator

A quick remark on this workflow: this only works for users that are not listed as admins. Any username that is listed as an admin in the JupyterHub configuration file will automatically have authorization to login when they sign up. You can add admins by adding this line to the configuration file:

c.Authenticator.admin_users = { 'johnsnow' }

Admins still need to sign up in order to define a password, but they already have authorization to login.

Open SignUp

Although the default workflow can be useful for avoiding unwanted people to enter the system, it can lead to a bottleneck if you have a lot of users that need to quickly enter the system. So, one option is to allow everyone in, and later block any users you don’t want to access your system.

To do this, you need to activate the Open SignUp feature on Native Authenticator, which is done by adding the following line to the JupyterHub configuration file:

c.NativeAuthenticator.open_signup = True

Now the workflow is much simpler:

Workflow of Native Authenticator when you configured an Open Signup

Increasing password security

We wanted to give an option to increase security, if needed. So, with Native Authenticator you can add verification of strong passwords. But, what is indeed a "strong password"? We followed the NSIT guidelines described beautifully on this post.

The first thing is that we added an option to see the password while typing. As said on the post:

Typos are common when entering passwords, and when characters turn into dots as soon as they’re typed, it’s difficult to tell where you went wrong. This motivates users to pick shorter passwords that they’re less likely to mess up

So, we added this on SignUp and Change Password (and soon to the login as well 🙃).

Example of input box for passwords with hidden password (left) and open text password (right)

Also, you can add a verification for a minimum number of characters in a password by adding the value in the configuration file:

c.NativeAuthenticator.minimum_password_length = 10

This will prevent users to signup if the password is less then 10 characters. An error message appears in case the password is not good enough:

Error indicating the password is not long enough

Additionally, you can add a verification to avoid common passwords. Instead of asking for users to add special characters or upper case letters, the guide suggests to check for common passwords instead. Passwords such as 123456 or qwerty are common and not nearly secure enough. We used a list of 10 thousand common passwords that will be checked when this option is activated. To activate this verification add to the configuration file:

c.NativeAuthenticator.check_common_password = True

Avoid throttling of failed login

Another thing that increases security is to block users when they’ve had a number of failed logins. In Native Authenticator you can activate this behavior. The system will add a count for every consecutive failed login attempt. If the user exceeds the number of attempts that is permitted, it will check if some time has passed since the last attempt. If not enough time has passed, the system will keep them blocked until they wait a while longer before attempting to sign-in again.

As you can imagine, both the number of attempts permitted and the wait time are configurable.

c.NativeAuthenticator.allowed_failed_logins = 3
c.NativeAuthenticator.seconds_before_next_try = 1200

How we accomplished this

To construct this new Authenticator, Project Jupyter decided to get an intern (me!) through the Outreachy Program. The description of the project was made in advance and potential interns applied by resolving a micro-task defined in a specific repository. The main workflow was:

  • Create a profile on the Outreachy website;
  • Submit a pull request for a micro-task of the project you want to participate;
  • Fill a more complete profile about yourself;
  • Wait for the selection 🤓

The full presentation of the Project Jupyter participation, a list of mentors, and more details can be seen on this post, and more technical details on the micro-task I submitted can be seen here.

Technical details and day-to-day development

There were a lot of struggles to get things going. As part of the Outreachy program, I had to write about the tasks and struggles I had in posts on my personal blog. If you would like to check the details for how the process went at each you can checkout each of the available posts:

See ya :)

--

--

A happy dev, in love with data science and podcaster at Pizza de Dados