# Is there a recommended facility for routing users to services?

**URL:** <https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309>\
**Category:** JupyterHub\
**Created:** [August 12, 2022, 1:47am UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309 "2022-08-12T01:47:06Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![adpatter](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/adpatter/32/21229_2.png) [@adpatter](https://discourse.jupyter.org/u/adpatter)\
**Post date:** [August 12, 2022, 1:47am UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309/1 "2022-08-12T01:47:06Z")

</div>

We have several JupyterServer services running; they have been configured to authorize access to their respective group of users. The users are able to go directly to their service if they have the service URL; however, I would like provide all users with a single URL and route users to their assigned service after they have authenticated.

The only way I can think of doing it at present is to write a spawner that redirects the user to their assigned service. However, I was wondering if there is a designated routing facility for this. In other words, perhaps there is some way to say in the config file or some other means that userA should get routed to a particular URL for serviceA.

---

<div class="post-metadata">

**Author:** ![minrk](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/minrk/32/13_2.png) [@minrk](https://discourse.jupyter.org/u/minrk)\
**Post date:** [August 12, 2022, 8:47am UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309/2 "2022-08-12T08:47:24Z")

</div>

There is [JupyterHub.default\_url](https://jupyterhub.readthedocs.io/en/stable/api/app.html#jupyterhub.app.JupyterHub.default_url) that lets you pick the URL users are sent to _by default_ after login. This can be a fixed string, or a callable that takes the user into account and returns a string.

For example:

```python
def service_url(handler):
    user = handler.current_user
    groups = { group.name for group in user.groups }
    if "group-x" in groups:
        return "/services/x"
    else:
        return "/hub/home" # home page, not launching their default server

c.JupyterHub.default_url = service_url

```

This does _not_ modify things like ‘start my server’. If you want to _remove_ the notion of users having their own servers, that’s going far enough from JupyterHub’s standard behavior that I think it starts to make sense to use [api only mode](https://jupyterhub.readthedocs.io/en/stable/reference/api-only.html) where you present your own UI and only use JupyterHub via the API. This is what Binder does, for instance.

---

<div class="post-metadata">

**Author:** ![adpatter](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/adpatter/32/21229_2.png) [@adpatter](https://discourse.jupyter.org/u/adpatter)\
**Post date:** [August 17, 2022, 6:46pm UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309/3 "2022-08-17T18:46:56Z")

</div>

@minrk When I try to get the current user from the handler object (RootHandler) that is passed to service\_url, it is set to None. Below I have pasted my service\_url function, which prints out various objects that I can get to from within the service\_url function and I have pasted the output of those print statements. Do you know where else I could get the current user from within the service\_url function?

```auto
def service_url(handler):
    print('service_url')
    try:
        pprint.pprint(vars(handler))
        pprint.pprint(vars(vars(handler)['application']))
        pprint.pprint(vars(vars(handler)['request']))
        async def fn():
            try:
                value = await handler.get_current_user()
                print("get_current_user", value)
            except Exception as e:
                print(str(e))

        io_loop = tornado.ioloop.IOLoop.current()
        io_loop.spawn_callback(fn)

    except Exception as e:
        print(str(e))

    service_name = 'shared-notebook'
    url = f'https://jupyterhub.mentoracademy.org/services/{service_name}/lab'
    return url

```

```auto
service_url
{'_auto_finish': True,
 '_finished': False,
 '_headers': <tornado.httputil.HTTPHeaders object at 0x7fa1ca9faf20>,
 '_headers_written': False,
 '_jupyterhub_user': None,
 '_new_cookie': <SimpleCookie: jupyterhub-hub-login=''>,
 '_prepared_future': None,
 '_reason': 'OK',
 '_status_code': 200,
 '_transforms': [],
 '_write_buffer': [],
 'application': <tornado.web.Application object at 0x7fa1cabb9720>,
 'expanded_scopes': set(),
 'parsed_scopes': {},
 'path_args': [],
 'path_kwargs': {},
 'request': HTTPServerRequest(protocol='http', host='jupyterhub.mentoracademy.org', method='GET', uri='/hub/', version='HTTP/1.1', remote_ip='10.0.10.177'),
 'ui': {'_tt_modules': <tornado.web._UIModuleNamespace object at 0x7fa1ca9f9db0>,
        'modules': <tornado.web._UIModuleNamespace object at 0x7fa1ca9f9db0>}}
{'default_host': None,
 'default_router': <tornado.web._ApplicationRouter object at 0x7fa1ca9eda80>,
 'settings': {'active_server_limit': 0,
              'activity_resolution': 30,
              'admin_access': False,
              'admin_users': {'adpatter'},
              'allow_named_servers': False,
              'api_page_default_limit': 50,
              'api_page_max_limit': 200,
              'app': <jupyterhub.app.JupyterHub object at 0x7fa1cb99bf10>,
              'authenticate_prometheus': True,
              'authenticator': <oauthenticator.github.GitHubOAuthenticator object at 0x7fa1caabe2c0>,
              'base_url': '/',
              'concurrent_spawn_limit': 100,
              'config': {'Authenticator': {'admin_users': {'adpatter'},
                                           'allowed_users': {'adpatter',
                                                             'faranalytics'}},
                         'ConfigurableHTTPProxy': {'auth_token': '8af5b4e9ca1b3f845da1298d35a02ed5'},
                         'GitHubOAuthenticator': {'client_id': '519a817b6bb39563b210',
                                                  'client_secret': 'efbbb02526c9d5a9d3f48dfab7d7d8b91225b3b8',
                                                  'oauth_callback_url': 'https://jupyterhub.mentoracademy.org/hub/oauth_callback'},
                         'JupyterHub': {'authenticator_class': <class 'oauthenticator.github.GitHubOAuthenticator'>,
                                        'bind_url': 'http://0.0.0.0:8000',
                                        'config_file': '/etc/jupyterhub/jupyterhub_config.py',
                                        'default_url': <function service_url at 0x7fa1cb8279a0>,
                                        'hub_bind_url': 'http://0.0.0.0:8081',
                                        'hub_connect_ip': '10.0.10.177',
                                        'load_groups': {'shared-notebook': ['faranalytics',
                                                                            'adpatter']},
                                        'load_roles': [{'groups': ['shared-notebook'],
                                                        'name': 'shared-notebook',
                                                        'scopes': ['access:services!service=shared-notebook'],
                                                        'services': ['shared-notebook']}],
                                        'log_level': 10,
                                        'services': [{'api_token': '823249464ad5466196a12703bb8aafd7',
                                                      'name': 'shared-notebook',
                                                      'url': 'http://10.0.16.179:8888'}]}},
              'cookie_max_age_days': 14.0,
              'cookie_secret': b'\x85/\xa2\x06\x81]\xa7\xc9\x7f#!\x8a'
                               b';\xea\xe5\xf4>C\xf6h3Wb\xfc\xear\x1f\xd6'
                               b'\x86\xa1\x16\xf6',
              'db': <sqlalchemy.orm.session.Session object at 0x7fa1caeda500>,
              'default_server_name': '',
              'default_url': <function service_url at 0x7fa1cb8279a0>,
              'domain': '',
              'eventlog': <jupyter_telemetry.eventlog.EventLog object at 0x7fa1cb9d52d0>,
              'hub': <Hub 0.0.0.0:8081>,
              'implicit_spawn_seconds': 0.0,
              'internal_authorities': {'hub-ca': None,
                                       'notebooks-ca': None,
                                       'proxy-api-ca': None,
                                       'proxy-client-ca': None,
                                       'services-ca': None},
              'internal_certs_location': 'internal-ssl',
              'internal_ssl': False,
              'internal_ssl_ca': '',
              'internal_ssl_cert': '',
              'internal_ssl_key': '',
              'internal_trust_bundles': {},
              'jinja2_env': <jinja2.environment.Environment object at 0x7fa1cab98610>,
              'jinja2_env_sync': <jinja2.environment.Environment object at 0x7fa1cab9ba60>,
              'log': <Logger JupyterHub (DEBUG)>,
              'log_function': <function log_request at 0x7fa1cb95e320>,
              'login_url': '/hub/login',
              'logout_url': '/hub/logout',
              'named_server_limit_per_user': 0,
              'oauth_no_confirm_list': set(),
              'oauth_provider': <jupyterhub.oauth.provider.JupyterHubOAuthServer object at 0x7fa1caabd0f0>,
              'proxy': <jupyterhub.proxy.ConfigurableHTTPProxy object at 0x7fa1cac8bfd0>,
              'redirect_to_server': True,
              'services': {'shared-notebook': <Service(name=shared-notebook)>},
              'shutdown_on_logout': False,
              'spawn_throttle_retry_range': (30, 60),
              'spawner_class': <class 'jupyterhub.spawner.LocalProcessSpawner'>,
              'static_handler_class': <class 'jupyterhub.handlers.static.CacheControlStaticFilesHandler'>,
              'static_path': '/usr/local/share/jupyterhub/static',
              'static_url_prefix': '/hub/static/',
              'statsd': <jupyterhub.emptyclass.EmptyClass object at 0x7fa1cab73880>,
              'subdomain_host': '',
              'template_path': ['/usr/local/share/jupyterhub/templates'],
              'template_vars': {},
              'trusted_alt_names': [],
              'users': {},
              'version_hash': '20220817183744'},
 'transforms': [],
 'ui_methods': {},
 'ui_modules': {'Template': <class 'tornado.web.TemplateModule'>,
                'linkify': <class 'tornado.web._linkify'>,
                'xsrf_form_html': <class 'tornado.web._xsrf_form_html'>},
 'wildcard_router': <tornado.web._ApplicationRouter object at 0x7fa1cabb92d0>}
{'_cookies': <SimpleCookie: jupyterhub-hub-login='2|1:0|10:1660761111|20:jupyterhub-hub-login|44:MjEwMmYyZDdlNjNmNGFkZTg0YWU2N2NkNDA2NWUzOTg=|c6bd0e642f83a39fdcf2934d9540aab859beac9d354170eb1f3b1b5bea4a60d9' jupyterhub-session-id='4f2b0f22e6ca4bec9d806d9a108352f6'>,
 '_finish_time': None,
 '_start_time': 1660761472.4006698,
 'arguments': {},
 'body': b'',
 'body_arguments': {},
 'connection': <tornado.http1connection.HTTP1Connection object at 0x7fa1ca9fa650>,
 'files': {},
 'headers': <tornado.httputil.HTTPHeaders object at 0x7fa1ca9fa830>,
 'host': 'jupyterhub.mentoracademy.org',
 'host_name': 'jupyterhub.mentoracademy.org',
 'method': 'GET',
 'path': '/hub/',
 'protocol': 'http',
 'query': '',
 'query_arguments': {},
 'remote_ip': '10.0.10.177',
 'server_connection': <tornado.http1connection.HTTP1ServerConnection object at 0x7fa1ca9fa1a0>,
 'uri': '/hub/',
 'version': 'HTTP/1.1'}
[I 2022-08-17 18:37:52.413 JupyterHub log:189] 302 GET /hub/ -> https://jupyterhub.mentoracademy.org/services/shared-notebook/lab (@10.0.10.177) 12.96ms
get_current_user None

```

---

<div class="post-metadata">

**Author:** ![adpatter](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/adpatter/32/21229_2.png) [@adpatter](https://discourse.jupyter.org/u/adpatter)\
**Post date:** [August 17, 2022, 8:10pm UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309/4 "2022-08-17T20:10:23Z")

</div>

I’ve discovered that c.JupyterHub.default\_url actually gets called before the user has authenticated. I guess it makes sense that it wouldn’t contain the user’s information if that is the case.

---

<div class="post-metadata">

**Author:** ![minrk](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/minrk/32/13_2.png) [@minrk](https://discourse.jupyter.org/u/minrk)\
**Post date:** [August 18, 2022, 7:30am UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309/5 "2022-08-18T07:30:11Z")

</div>

`handler.current_user` is a synchronous reference to the current authenticated user, you shouldn’t need to call the async `get_current_user`.

---

<div class="post-metadata">

**Author:** ![adpatter](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/adpatter/32/21229_2.png) [@adpatter](https://discourse.jupyter.org/u/adpatter)\
**Post date:** [August 18, 2022, 12:11pm UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309/6 "2022-08-18T12:11:55Z")

</div>

@minrk The current\_user property is also None. I tried calling the asyn function in case it might yield something different. The default\_url function is getting called prior to the user completing authentication; I can see that in the logs. We are using the GitHub Authenticator.

Please let me know if you have any ideas.

Thank you.

---

<div class="post-metadata">

**Author:** ![minrk](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/minrk/32/13_2.png) [@minrk](https://discourse.jupyter.org/u/minrk)\
**Post date:** [August 19, 2022, 10:04am UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309/7 "2022-08-19T10:04:22Z")

</div>

Try returning `handler.base_url` if the user is None. I think it will get called again when the user’s authenticated.

---

<div class="post-metadata">

**Author:** ![adpatter](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/adpatter/32/21229_2.png) [@adpatter](https://discourse.jupyter.org/u/adpatter)\
**Post date:** [August 19, 2022, 11:05am UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309/8 "2022-08-19T11:05:52Z")

</div>

handler.base\_url is `/` in our case. If I return handler.base\_url if handler.current\_user is None, it causes the server to do numerous 302 redirects; it doesn’t get to the authentication page. We are using version 2.3.1.

---

<div class="post-metadata">

**Author:** ![minrk](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.jupyter.org/minrk/32/13_2.png) [@minrk](https://discourse.jupyter.org/u/minrk)\
**Post date:** [August 22, 2022, 8:56am UTC](https://discourse.jupyter.org/t/is-there-a-recommended-facility-for-routing-users-to-services/15309/9 "2022-08-22T08:56:08Z")

</div>

Sorry, this behavior is a bit confusing and not quite what I expected. `default_url` is _both_ the unconditional redirect for `/hub/` _and_ the default destination from the login page. So if you point it to itself, it’s going to redirect without logging in. Return `/hub/login` when there’s no user logged in and it should work, sending to your default page after login.
