    async def get_authenticated_user(self, handler, data):
        """Authenticate the user who is attempting to log in

        Returns user dict if successful, None otherwise.

        This calls `authenticate`, which should be overridden in subclasses,
        normalizes the username if any normalization should be done,
        and then validates the name in the allowed set.

        This is the outer API for authenticating a user.
        Subclasses should not override this method.

        The various stages can be overridden separately:
         - `authenticate` turns formdata into a username
         - `normalize_username` normalizes the username
         - `check_blocked_users` check against the blocked usernames
         - `allow_all` is checked
         - `check_allowed` checks against the allowed usernames
         - `is_admin` check if a user is an admin

        .. versionchanged:: 0.8
            return dict instead of username
        """
        authenticated = await maybe_future(self.authenticate(handler, data))
        if authenticated is None:
            return
        if isinstance(authenticated, dict):
            if 'name' not in authenticated:
                raise ValueError(f"user missing a name: {authenticated!r}")
        else:
            authenticated = {'name': authenticated}
        authenticated.setdefault('auth_state', None)
        # Leave the default as None, but reevaluate later post-allowed-check
        authenticated.setdefault('admin', None)

        # normalize the username
        authenticated['name'] = username = self.normalize_username(
            authenticated['name']
        )
        if not self.validate_username(username):
            self.log.warning("Disallowing invalid username %r.", username)
            return

        blocked_pass = await maybe_future(
            self.check_blocked_users(username, authenticated)
        )

        if not blocked_pass:
            self.log.warning("User %r blocked. Stop authentication", username)
            return

        allowed_pass = self.allow_all
        if not allowed_pass:
            allowed_pass = await maybe_future(
                self.check_allowed(username, authenticated)
            )

        if allowed_pass:
            if authenticated['admin'] is None:
                authenticated['admin'] = await maybe_future(
                    self.is_admin(handler, authenticated)
                )

            authenticated = await self.run_post_auth_hook(handler, authenticated)

            return authenticated
        else:
            self.log.warning("User %r not allowed.", username)
            return
Authenticate the user who is attempting to log in

Returns user dict if successful, None otherwise.

This calls `authenticate`, which should be overridden in subclasses,
normalizes the username if any normalization should be done,
and then validates the name in the allowed set.

This is the outer API for authenticating a user.
Subclasses should not override this method.

The various stages can be overridden separately:
- `authenticate` turns formdata into a username
- `normalize_username` normalizes the username
- `check_blocked_users` check against the blocked usernames
- `allow_all` is checked
- `check_allowed` checks against the allowed usernames
- `is_admin` check if a user is an admin

.. versionchanged:: 0.8
return dict instead of username
jupyterhub/auth.py