Need Help Setting Up Okta oauth

Hey Guys, I would be appreciative if someone here could give me an example of an Okta OAuth script for JupyterHub? I didn’t actually set up our JupyterHub server myself but I have admin access to it. Basically, the original use case for the server was for some of our Finance people to learn Python but now we have a Financial Analysts using it for far more than that so I need to harden the server security-wise and migrate users from PAM authentication to preferably Okta. I am a System & Network Admin for my organization but this will be the first time that I will be setting up Okta OAuth on an app itself (and not just on the Okta’s end).

Jupyterhub_config.py:

import os

c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'
c.DummyAuthenticator.password = redacted
c.Authenticator.whitelist = { redacted }
c.Authenticator.admin_users = redacted

c.JupyterHub.hub_connect_ip = redacted
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
c.DockerSpawner.image = redacted
c.DockerSpawner.use_internal_ip = True
c.JupyterHub.hub_ip = '0.0.0.0'
c.JupyterHub.hub_port = 8080

notebook_dir = os.environ.get('DOCKER_NOTEBOOK_DIR') or '/home/jovyan/'
c.DockerSpawner.notebook_dir = notebook_dir

# Mount the real user's Docker volume on the host to the notebook user's
# notebook directory in the container
c.DockerSpawner.volumes = { 'jupyterhub-user-{username}': notebook_dir }
c.DockerSpawner.extra_create_kwargs.update({ 'volume_driver': 'local' })

# Remove containers once they are stopped
c.DockerSpawner.remove_containers = True

# For debugging arguments passed to spawned containers
c.DockerSpawner.debug = True

Cheers,
@consideRatio

Hi thanks for posting the question on Discourse @XChainfireX!

I have not configured a the GenericOAuthenticator to use Okta before unless done from a Z2JH deployment. I’m first of all posting a reference example for that below.

Furthermore, in the Z2JH deployent I can easily set environment variables. Setting some environment variables needs to be done to configure everything correctly until this issue is resolved. How you would like to do this is not obvious to me, it depends on how your JupyterHub is running I guess.

I do not think you could add the environment variables from within the jupyterhub_config.py itself as by that time it may be to late to pass them along to the GenericOAuthenticator, but perhaps not, I’m not sure.

Attempted config for a JH with DockerSpawner (jupyterhub_config.py)

# See: https://example.okta.com/oauth2/.well-known/openid-configuration
# NOTE: USERDATA / USERINFO - same thing
# FIXME:
make_sure_that_these_environment_variables_are_available_somehow = {
    'OAUTH2_AUTHORIZE_URL': 'https://example.okta.com/oauth2/v1/authorize'
    'OAUTH2_USERDATA_URL':  'https://example.okta.com/oauth2/v1/userinfo'
    'OAUTH2_TOKEN_URL':     'https://example.okta.com/oauth2/v1/token'
    'OAUTH_CALLBACK_URL':   'https://jupyter.example.com/hub/oauth_callback'
}

c.JupyterHub.spawner_class = 'oauthenticator.generic.GenericOAuthenticator'

# See: https://developer.okta.com/docs/api/resources/oidc#userinfo
# See: https://developer.okta.com/docs/api/resources/oidc#scope-dependent-claims-not-always-returned
c.GenericOAuthenticator.update({
    'client_id': '<your-okta-application-client-id>',
    'client_secret': '<your-okta-application-client-secret>',
    'login_service': 'Okta',
    'username_key': 'preferred_username',
    'scope' = [
        'openid',
        'profile',
        'email',
        'offline_access',
    ]
})

A Z2JH config (config.yaml)

hub:
  extraEnv:
    # See: https://example.okta.com/oauth2/.well-known/openid-configuration
    OAUTH2_AUTHORIZE_URL: https://example.okta.com/oauth2/v1/authorize
    OAUTH2_USERDATA_URL:  https://example.okta.com/oauth2/v1/userinfo
    OAUTH2_TOKEN_URL:     https://example.okta.com/oauth2/v1/token
    OAUTH_CALLBACK_URL:   https://jupyter.example.com/hub/oauth_callback

auth:
  type: custom
  custom:
    className: oauthenticator.generic.GenericOAuthenticator
    config:
      client_id:     <your-okta-application-client-id>
      client_secret: <your-okta-application-client-secret>
      login_service: Okta
      username_key:  preferred_username
  scopes:
    # See: https://developer.okta.com/docs/api/resources/oidc#userinfo
    # See: https://developer.okta.com/docs/api/resources/oidc#scope-dependent-claims-not-always-returned
    - openid
    - profile
    - email
    - offline_access
  admin:
    access: false
    users:
      - erik.sundell@example.com

Using a social identity provider (IdP) like Google behind Okta bugs =/

If you let Okta redirect you to a social identity provider like Microsoft or Facebook, then when you return to JupyterHub all the information that were supposed to return in the query parameters like state=... have been lost. Instead, you get fromLogin=true. With that information, JupyterHub cannot know you have signed in. The workaround solution is to pass along a query parameter (idp=<your-social-idp-id>) in the initial authorize request to Okta. The crux of doing this is that then you have locked into a specific identity provider and also that is not something we currently can do from the OAuthenticator.

Okta is tracking this bug internally as OKTA-213686. And the issue of not being able to pass along extra parameters to the initial authorize request is described in this issue:

We tried using this jupyterhub_config.py file for Okta oAuth set up. We received following error while executing the jupyterhub_config.py file. Our jupyterhub is on AWS EC2. Any leads on this would be much helpful.

ERROR:
Bad config encountered during initialization: The ‘spawner_class’ trait of a JupyterHub instance expected a subclass of ‘jupyterhub.spawner.Spawner’, not the GenericOAuthenticator GenericOAuthenticator.

From the error it seems that you are attempting to set spawner className. You should set auth className to GenericOAuthenticator GenericOAuthenticator. Could you post your config?

Sure attached the config file here.

The line c.JupyterHub.spawner_class = 'oauthenticator<snipped>' is incorrect. That should be c.JupyterHub.authenticator_class = 'oauthenticator<snipped>'.