A multi-user server for Jupyter notebooks that provides authentication, spawning, and proxying for multiple users simultaneously
—
JupyterHub provides comprehensive tools and mixins for creating Hub-authenticated single-user servers and Jupyter server extensions. This system enables notebook servers to authenticate with the Hub and integrate seamlessly with the multi-user environment.
Core function for creating Hub-authenticated single-user server applications.
def make_singleuser_app(cls):
"""
Create a single-user application class with Hub authentication.
Decorates a Jupyter server application class to add JupyterHub
authentication and integration capabilities.
Args:
cls: Jupyter server application class to enhance
Returns:
Enhanced application class with Hub authentication
"""
# Example usage
from jupyter_server.serverapp import ServerApp
from jupyterhub.singleuser import make_singleuser_app
@make_singleuser_app
class SingleUserNotebookApp(ServerApp):
"""Hub-authenticated notebook server"""
passBase classes for implementing Hub-authenticated request handlers in single-user servers.
class HubAuthenticatedHandler(BaseHandler):
"""
Base handler for Hub-authenticated requests in single-user servers.
Provides authentication integration with JupyterHub and handles
user verification and permission checking.
"""
@property
def hub_auth(self):
"""Hub authentication helper instance"""
@property
def hub_user(self):
"""Current Hub user information"""
@property
def current_user(self):
"""Current authenticated user (compatible with Jupyter handlers)"""
def get_current_user(self):
"""
Get current user from Hub authentication.
Returns:
User information dictionary or None if not authenticated
"""
def hub_user_from_cookie(self, cookie_name, cookie_value):
"""
Get user information from Hub cookie.
Args:
cookie_name: Name of the authentication cookie
cookie_value: Cookie value
Returns:
User information or None if invalid
"""
def check_hub_user(self, model):
"""
Check if current user matches the Hub user for this server.
Args:
model: User model to verify
Returns:
True if user is authorized
"""
class HubOAuthHandler(HubAuthenticatedHandler):
"""
OAuth-based Hub authentication handler for single-user servers.
Handles OAuth token validation and user authorization.
"""
def get_current_user_oauth(self, handler):
"""
Get current user via OAuth token validation.
Args:
handler: Request handler instance
Returns:
User information from OAuth validation
"""Extension system for integrating JupyterHub with Jupyter server.
def _jupyter_server_extension_points() -> List[Dict[str, str]]:
"""
Jupyter server extension discovery function.
Makes the JupyterHub single-user extension discoverable
by the Jupyter server extension system.
Returns:
List of extension metadata dictionaries with module and app info
"""
class JupyterHubSingleUser(ExtensionApp):
"""
Jupyter server extension for JupyterHub single-user integration.
Provides the extension implementation for Hub-authenticated
single-user servers.
"""
name: str = 'jupyterhub-singleuser'
description: str = 'JupyterHub single-user server extension'
def initialize_settings(self):
"""Initialize extension settings"""
def initialize_handlers(self):
"""Initialize request handlers"""
def initialize_templates(self):
"""Initialize Jinja2 templates"""Configuration and startup utilities for single-user servers.
class SingleUserNotebookApp(JupyterApp):
"""
Single-user notebook server application.
Main application class for JupyterHub single-user servers
with Hub authentication and integration.
"""
# Hub connection configuration
hub_host: str # JupyterHub host
hub_prefix: str # Hub URL prefix
hub_api_url: str # Hub API URL
# Authentication configuration
user: str # Username for this server
group: str # User group
cookie_name: str # Hub authentication cookie name
# Server configuration
base_url: str # Server base URL
default_url: str # Default redirect URL
# OAuth configuration (optional)
oauth_client_id: str # OAuth client ID
oauth_redirect_uri: str # OAuth redirect URI
def initialize(self, argv=None):
"""
Initialize single-user server.
Args:
argv: Command line arguments
"""
def start(self):
"""Start the single-user server"""
def make_singleuser_app(self):
"""Create single-user application with Hub integration"""
def main(argv=None) -> int:
"""
Main entry point for jupyterhub-singleuser command.
Args:
argv: Command line arguments
Returns:
Exit code (0 for success)
"""Tools for single-user servers to communicate with the JupyterHub API.
class HubAPIClient:
"""
API client for single-user servers to communicate with Hub.
Provides methods for servers to report status and interact
with the central Hub.
"""
def __init__(self, hub_api_url: str, api_token: str):
"""
Initialize Hub API client.
Args:
hub_api_url: JupyterHub API base URL
api_token: API token for authentication
"""
async def get_user(self, username: str) -> Dict[str, Any]:
"""
Get user information from Hub.
Args:
username: Username to look up
Returns:
User information dictionary
"""
async def update_server_activity(self):
"""Update server activity timestamp in Hub"""
async def report_server_status(self, status: str):
"""
Report server status to Hub.
Args:
status: Server status ('running', 'ready', 'error', etc.)
"""
async def get_server_info(self, username: str, server_name: str = '') -> Dict[str, Any]:
"""
Get server information from Hub.
Args:
username: Server owner username
server_name: Named server (empty for default)
Returns:
Server information dictionary
"""# Create Hub-authenticated server application
from jupyter_server.serverapp import ServerApp
from jupyterhub.singleuser import make_singleuser_app
@make_singleuser_app
class MyHubServer(ServerApp):
"""Custom Hub-authenticated server"""
# Custom configuration
custom_setting = Unicode(
'default_value',
config=True,
help="Custom server setting"
)
def initialize_settings(self):
"""Initialize custom settings"""
super().initialize_settings()
# Add custom settings to web app
self.web_app.settings.update({
'custom_setting': self.custom_setting
})
# Start the server
if __name__ == '__main__':
MyHubServer.launch_instance()from jupyterhub.singleuser.mixins import HubAuthenticatedHandler
from tornado import web
class CustomAPIHandler(HubAuthenticatedHandler):
"""Custom API endpoint with Hub authentication"""
@web.authenticated
async def get(self):
"""GET endpoint with user authentication"""
user = self.current_user
if not user:
raise web.HTTPError(401, "Authentication required")
# Verify user matches server owner
if user['name'] != self.hub_user['name']:
raise web.HTTPError(403, "Access denied")
# Custom API logic
data = await self.get_user_data(user)
self.write({'data': data, 'user': user['name']})
async def get_user_data(self, user):
"""Get data specific to authenticated user"""
# Your custom data retrieval logic
return {'files': [], 'settings': {}}
# Register handler in server application
class CustomHubServer(SingleUserNotebookApp):
"""Server with custom API endpoints"""
def initialize_handlers(self):
"""Add custom handlers"""
super().initialize_handlers()
# Add custom API endpoint
self.web_app.add_handlers('.*', [
(r'/api/custom', CustomAPIHandler),
])import os
from jupyterhub.singleuser import HubAPIClient
class IntegratedServer(SingleUserNotebookApp):
"""Server with Hub API integration"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Initialize Hub API client
self.hub_client = HubAPIClient(
hub_api_url=os.environ['JUPYTERHUB_API_URL'],
api_token=os.environ['JUPYTERHUB_API_TOKEN']
)
async def initialize(self):
"""Initialize with Hub registration"""
await super().initialize()
# Report server startup to Hub
await self.hub_client.report_server_status('starting')
# Get user information from Hub
user_info = await self.hub_client.get_user(self.user)
self.log.info(f"Starting server for user: {user_info['name']}")
def start(self):
"""Start server and report to Hub"""
super().start()
# Report ready status
asyncio.create_task(self.hub_client.report_server_status('ready'))
async def periodic_status_update(self):
"""Periodic status updates to Hub"""
while True:
try:
await self.hub_client.update_server_activity()
await asyncio.sleep(300) # Update every 5 minutes
except Exception as e:
self.log.error(f"Failed to update Hub status: {e}")
await asyncio.sleep(60) # Retry after 1 minutefrom jupyterhub.singleuser.extension import JupyterHubSingleUser
from jupyter_server.extension.application import ExtensionApp
class CustomExtension(ExtensionApp):
"""Custom extension with Hub integration"""
name = 'my-hub-extension'
description = 'Custom JupyterHub extension'
def initialize_settings(self):
"""Initialize extension settings"""
# Get Hub authentication info
hub_user = self.serverapp.web_app.settings.get('hub_user')
if hub_user:
self.log.info(f"Extension initialized for user: {hub_user['name']}")
# Add custom settings
self.serverapp.web_app.settings.update({
'custom_extension_enabled': True
})
def initialize_handlers(self):
"""Initialize extension handlers"""
from .handlers import CustomHandler
self.handlers.extend([
(r'/my-extension/api/.*', CustomHandler),
])
# Register extension
def _jupyter_server_extension_paths():
"""Extension discovery function"""
return [{
'module': 'my_extension',
'app': CustomExtension
}]class OAuthIntegratedServer(SingleUserNotebookApp):
"""Single-user server with OAuth integration"""
oauth_client_id = Unicode(
config=True,
help="OAuth client ID for Hub integration"
)
def initialize_settings(self):
"""Initialize OAuth settings"""
super().initialize_settings()
# Configure OAuth settings
if self.oauth_client_id:
self.web_app.settings.update({
'oauth_client_id': self.oauth_client_id,
'oauth_redirect_uri': f'{self.base_url}oauth-callback'
})
def initialize_handlers(self):
"""Add OAuth handlers"""
super().initialize_handlers()
# Add OAuth callback handler
self.web_app.add_handlers('.*', [
(r'/oauth-callback', OAuthCallbackHandler),
(r'/oauth-login', OAuthLoginHandler),
])
class OAuthCallbackHandler(HubAuthenticatedHandler):
"""Handle OAuth callback from Hub"""
async def get(self):
"""Process OAuth authorization callback"""
code = self.get_argument('code', None)
state = self.get_argument('state', None)
if not code:
raise web.HTTPError(400, "Missing authorization code")
# Exchange code for token with Hub
token_info = await self.exchange_oauth_code(code)
# Validate user and redirect
user = await self.validate_oauth_token(token_info['access_token'])
self.redirect(self.get_argument('next', '/'))
async def exchange_oauth_code(self, code):
"""Exchange OAuth code for access token"""
# Implementation depends on Hub OAuth configuration
passclass MultiServerManager:
"""Manage multiple servers for a single user"""
def __init__(self, hub_client, user_name):
self.hub_client = hub_client
self.user_name = user_name
self.servers = {}
async def get_user_servers(self):
"""Get all servers for user from Hub"""
user_info = await self.hub_client.get_user(self.user_name)
return user_info.get('servers', {})
async def start_named_server(self, server_name, server_options=None):
"""Start a named server for user"""
response = await self.hub_client.start_server(
username=self.user_name,
server_name=server_name,
options=server_options or {}
)
return response
async def stop_named_server(self, server_name):
"""Stop a named server"""
await self.hub_client.stop_server(
username=self.user_name,
server_name=server_name
)class HealthMonitoredServer(SingleUserNotebookApp):
"""Server with health monitoring and auto-restart"""
health_check_interval = Integer(
60,
config=True,
help="Health check interval in seconds"
)
def start(self):
"""Start server with health monitoring"""
super().start()
# Start health monitoring task
asyncio.create_task(self.health_monitor())
async def health_monitor(self):
"""Monitor server health and report to Hub"""
while True:
try:
# Check server health
health_status = await self.check_health()
# Report to Hub
status = 'healthy' if health_status else 'unhealthy'
await self.hub_client.report_server_status(status)
# Auto-restart if unhealthy
if not health_status:
self.log.warning("Server unhealthy, requesting restart")
await self.request_restart()
await asyncio.sleep(self.health_check_interval)
except Exception as e:
self.log.error(f"Health monitoring error: {e}")
await asyncio.sleep(60)
async def check_health(self):
"""Check server health status"""
try:
# Custom health check logic
return True
except Exception:
return False
async def request_restart(self):
"""Request server restart from Hub"""
await self.hub_client.report_server_status('restart_requested')class SharedResourceServer(SingleUserNotebookApp):
"""Server with shared resource access controls"""
def initialize_settings(self):
"""Initialize with resource access controls"""
super().initialize_settings()
# Get user's resource permissions from Hub
user_scopes = self.hub_user.get('scopes', [])
# Configure resource access based on scopes
self.web_app.settings.update({
'shared_storage_access': 'shared-storage' in user_scopes,
'gpu_access': 'gpu-resources' in user_scopes,
'admin_access': 'admin' in user_scopes
})
def initialize_handlers(self):
"""Add resource access handlers"""
super().initialize_handlers()
# Add shared resource handlers
self.web_app.add_handlers('.*', [
(r'/shared-storage/.*', SharedStorageHandler),
(r'/gpu-status', GPUStatusHandler),
])
class SharedStorageHandler(HubAuthenticatedHandler):
"""Handler for shared storage access"""
@web.authenticated
async def get(self, path):
"""Access shared storage with permission check"""
if not self.settings.get('shared_storage_access'):
raise web.HTTPError(403, "Shared storage access denied")
# Shared storage access logic
passInstall with Tessl CLI
npx tessl i tessl/pypi-jupyterhub