Setting up Binder with Jupyterhub OAUTH

Hi.

I have a Jupyterhub deployment configured with oauth2 against our internal provider, and it works fine. Users can launch their notebooks and the service tracks individual accounts.

I would like to have the Binder setup also authenticated, and if i understand correctly it relies on the Hub for this. I tried to follow this page
https://binderhub.readthedocs.io/en/latest/authentication.html

And the config looks like:

binderhub:
  config:
    BinderHub:
      auth_enabled: true
      hub_url: https://hub.cern.ch
      use_named_servers: true
  jupyterhub:
    singleuser:
      cmd: jupyterhub-singleuser
    hub:
      services:
        binder:
          oauth_redirect_uri: "https://binder.cern.ch/oauth_callback"
          oauth_client_id: "binder-oauth-client-test"
      allowNamedServers: true
      extraEnv:
        OAUTH2_AUTHORIZE_URL: https://oauth.web.cern.ch/OAuth/Authorize
        OAUTH2_TOKEN_URL: https://oauth.web.cern.ch/OAuth/Token
        OAUTH_CALLBACK_URL: https://hub.cern.ch/hub/oauth_callback
      extraConfig:
        hub_extra: |
          c.JupyterHub.redirect_to_server = False
        binder: |
          from kubespawner import KubeSpawner

          class BinderSpawner(KubeSpawner):
            def start(self):
                if 'image' in self.user_options:
                  # binder service sets the image spec via user options
                  self.image = self.user_options['image']
                return super().start()
          c.JupyterHub.spawner_class = BinderSpawner
    auth:
      type: custom
      custom:
        className: oauthenticator.generic.GenericOAuthenticator
        config:
          token_url: "https://oauth.web.cern.ch/OAuth/Token"
          userdata_url: "https://oauthresource.web.cern.ch/api/User"
          groupdata_url: "https://oauthresource.web.cern.ch/api/Group"
          userdata_method: GET
          userdata_params: {'state': 'state'}
          username_key: username

It’s using the binderhub helm chart 0.2.0-575fb2a to manage the deployment.

If i disable auth the binder deployment works up to the launch of the notebook (as auth is required in the hub), enabling binder’s auth i see the redirection to the hub working fine for authorization but eventually getting a permission denied:

A service is attempting to authorize with your JupyterHub account
JupyterHub service binder (oauth URL: https://binder.cern.ch/oauth_callback) would like permission to identify you. It will not be able to take actions on your behalf.

403 Forbidden: Authorization form must be sent from authorization page

That’s all i see in the hub’s logs as well, the POST request for oauth goes through ok but returns 403:

[W 2019-09-30 09:22:19.662 JupyterHub web:1782] 403 POST /hub/api/oauth2/authorize?client_id=binder-oauth-client-test&redirect_uri=https%3A%2F%2Fbinder.cern.ch%2Foauth_callback&response_type=code&state=<redacted> (194.12.165.129): Authorization form must be sent from authorization page

Any idea of what could be the issue?

Thanks!

Ricardo

1 Like

Hi Ricardo,

In the last part:

auth:
      type: custom
      custom:
        className: oauthenticator.generic.GenericOAuthenticator
        config:
          token_url: "https://oauth.web.cern.ch/OAuth/Token"
          userdata_url: "https://oauthresource.web.cern.ch/api/User"
          groupdata_url: "https://oauthresource.web.cern.ch/api/Group"
          userdata_method: GET
          userdata_params: {'state': 'state'}
          username_key: username

Make sure you have set:
client_id: “<oauth_client_id>”
client_secret: “<oauth_client_sercret>”

I had the same problem as you and this solved it.
I was also using Keycloak so this might be different for you.

1 Like

Thanks for your message.

I didn’t paste that part, but i do have it (it’s kept in a separate encrypted yaml file).

The oauth configuration for the jupyterhub part works nicely, it’s just the binderhub authorization that is giving me a permission denied.

Here’s an updated config with the missing redacted parts:

binderhub:
  config:
    BinderHub:
      auth_enabled: true
      hub_url: https://hub.cern.ch
      use_named_servers: true
  jupyterhub:
    singleuser:
      cmd: jupyterhub-singleuser
    hub:
      services:
        binder:
          oauth_redirect_uri: "https://binder.cern.ch/oauth_callback"
          oauth_client_id: "binder-oauth-client-test"
      allowNamedServers: true
      extraEnv:
        OAUTH2_AUTHORIZE_URL: https://oauth.web.cern.ch/OAuth/Authorize
        OAUTH2_TOKEN_URL: https://oauth.web.cern.ch/OAuth/Token
        OAUTH_CALLBACK_URL: https://hub.cern.ch/hub/oauth_callback
      extraConfig:
        hub_extra: |
          c.JupyterHub.redirect_to_server = False
        binder: |
          from kubespawner import KubeSpawner

          class BinderSpawner(KubeSpawner):
            def start(self):
                if 'image' in self.user_options:
                  # binder service sets the image spec via user options
                  self.image = self.user_options['image']
                return super().start()
          c.JupyterHub.spawner_class = BinderSpawner
    auth:
      type: custom
      custom:
        className: oauthenticator.generic.GenericOAuthenticator
        config:
          client_id: <redacted>
          client_secret: <redacted>
          token_url: "https://oauth.web.cern.ch/OAuth/Token"
          userdata_url: "https://oauthresource.web.cern.ch/api/User"
          groupdata_url: "https://oauthresource.web.cern.ch/api/Group"
          userdata_method: GET
          userdata_params: {'state': 'state'}
          username_key: username

After a quick look, I think this might be related to this one: https://github.com/jupyterhub/jupyterhub/issues/2284

After Wednesday I will be back. So if that link doesn’t help, I can try to help more.

Seems like this is it.

Using traefik as the Ingress controller in this deployment, and doing ssl termination at the Ingress. So it does look like it.

Checking how to pass the proper traefik annotations to get the referrer set correctly.

Confirmed that was the reason.

For reference it works with the following traefik annotation on the Ingress:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    ingress.kubernetes.io/custom-request-headers: X-Scheme:https
1 Like