Files
jupyterhub/examples/forced-login/jupyterhub_config.py
2025-04-24 14:53:40 +02:00

66 lines
2.2 KiB
Python

import json
from tornado import web
from tornado.httpclient import AsyncHTTPClient, HTTPClientError
from traitlets import Unicode
from jupyterhub.auth import Authenticator
from jupyterhub.utils import url_path_join
class ForcedLoginAuthenticator(Authenticator):
"""Authenticator to force login with a token provided by an external service
The external service issues tokens, which are exchanged for a username.
Visiting `/hub/login?login_token=...` logs in a user
Each token can be used only once.
"""
auto_login = True # begin login without prompt (token is in url)
allow_all = True # external login app controls this
token_provider_url = Unicode(
config=True, help="""The URL of the token/username provider"""
)
async def authenticate(self, handler, data):
token = handler.get_argument("login_token", None)
if not token:
raise web.HTTPError(
400, f"Login with external provider at {self.token_provider_url}"
)
client = AsyncHTTPClient()
try:
response = await client.fetch(
url_path_join(self.token_provider_url, "/login"),
method="POST",
headers={"Content-Type": "application/json"},
body=json.dumps({"token": token}),
)
except HTTPClientError as e:
self.log.info(
"Error exchanging token for username: %s",
e.response.body.decode("utf8", "replace"),
)
if e.code == 404:
raise web.HTTPError(
403,
f"Invalid token. Login with external provider at {self.token_provider_url}",
)
else:
raise
# pass through the response
return json.loads(response.body.decode())
c = get_config() # noqa
# use our Authenticator
c.JupyterHub.authenticator_class = ForcedLoginAuthenticator
# tell it where the external launch app is
c.ForcedLoginAuthenticator.token_provider_url = "http://127.0.0.1:9000/"
# local testing config (fake spawner, localhost only)
c.JupyterHub.ip = "127.0.0.1"
c.JupyterHub.spawner_class = "simple"