In case it’s useful, here’s a more complete copy of the code I ended up with. I wanted to keep my JH groups in sync with my AWS SSO groups, but you could use this pattern for any OAuth source.
class CustomOAuthenticator(GenericOAuthenticator):
# Automatically send the user to the OIDC login page instead of prompting them to click a button.
auto_login = True
# Allow all AUTHENTICATED users to log in.
allow_all = True
# Authenticator class (this) is now responsible for managing groups.
manage_groups = True
login_service = ...
username_claim = ...
authorize_url = ...
client_id = ...
client_secret = ...
oauth_callback_url ...
token_url = ...
userdata_url = ...
async def update_auth_model(self, auth_model):
"""
Because manage_groups is set to True, we need to include the user's groups in the returned auth model.
Ref: https://jupyterhub.readthedocs.io/en/stable/reference/authenticators.html#authenticator-managed-group-membership
"""
auth_model = await super().update_auth_model(auth_model)
# Build the user's list of groups in the normal fashion. The parent class will do this automatically,
# but only when checking for admin permissions. We want to do it for all users to return their groups!
user_info = auth_model["auth_state"][self.user_auth_state_key]
user_groups = self.get_user_groups(user_info)
# Include the new "groups" key in the auth_model.
auth_model["groups"] = user_groups
return auth_model
def claim_groups_key(self, user_info):
"""
Return the JupyterHub groups a user should be associated with:
- any AWS SSO group membership
- global collab group
"""
user_aws_sso_groups = aws_sso.user_groups.get(user_info['email'], [])
return [shared_group] + list(user_aws_sso_groups)
c.JupyterHub.authenticator_class = CustomOAuthenticator
Once I started down the custom class path, a lot of cool functionality became possible One problem I did quickly encounter was limitations with the base JupyterHub docker image I was using (via Z2JH), specifically it not having boto3
installed. So, had to customize that as well.
But in the end it works beautifully!