0
# Single-user Server Integration
1
2
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.
3
4
## Capabilities
5
6
### Single-user Application Factory
7
8
Core function for creating Hub-authenticated single-user server applications.
9
10
```python { .api }
11
def make_singleuser_app(cls):
12
"""
13
Create a single-user application class with Hub authentication.
14
15
Decorates a Jupyter server application class to add JupyterHub
16
authentication and integration capabilities.
17
18
Args:
19
cls: Jupyter server application class to enhance
20
21
Returns:
22
Enhanced application class with Hub authentication
23
"""
24
25
# Example usage
26
from jupyter_server.serverapp import ServerApp
27
from jupyterhub.singleuser import make_singleuser_app
28
29
@make_singleuser_app
30
class SingleUserNotebookApp(ServerApp):
31
"""Hub-authenticated notebook server"""
32
pass
33
```
34
35
### Hub Authentication Handlers
36
37
Base classes for implementing Hub-authenticated request handlers in single-user servers.
38
39
```python { .api }
40
class HubAuthenticatedHandler(BaseHandler):
41
"""
42
Base handler for Hub-authenticated requests in single-user servers.
43
44
Provides authentication integration with JupyterHub and handles
45
user verification and permission checking.
46
"""
47
48
@property
49
def hub_auth(self):
50
"""Hub authentication helper instance"""
51
52
@property
53
def hub_user(self):
54
"""Current Hub user information"""
55
56
@property
57
def current_user(self):
58
"""Current authenticated user (compatible with Jupyter handlers)"""
59
60
def get_current_user(self):
61
"""
62
Get current user from Hub authentication.
63
64
Returns:
65
User information dictionary or None if not authenticated
66
"""
67
68
def hub_user_from_cookie(self, cookie_name, cookie_value):
69
"""
70
Get user information from Hub cookie.
71
72
Args:
73
cookie_name: Name of the authentication cookie
74
cookie_value: Cookie value
75
76
Returns:
77
User information or None if invalid
78
"""
79
80
def check_hub_user(self, model):
81
"""
82
Check if current user matches the Hub user for this server.
83
84
Args:
85
model: User model to verify
86
87
Returns:
88
True if user is authorized
89
"""
90
91
class HubOAuthHandler(HubAuthenticatedHandler):
92
"""
93
OAuth-based Hub authentication handler for single-user servers.
94
95
Handles OAuth token validation and user authorization.
96
"""
97
98
def get_current_user_oauth(self, handler):
99
"""
100
Get current user via OAuth token validation.
101
102
Args:
103
handler: Request handler instance
104
105
Returns:
106
User information from OAuth validation
107
"""
108
```
109
110
### Jupyter Server Extension
111
112
Extension system for integrating JupyterHub with Jupyter server.
113
114
```python { .api }
115
def _jupyter_server_extension_points() -> List[Dict[str, str]]:
116
"""
117
Jupyter server extension discovery function.
118
119
Makes the JupyterHub single-user extension discoverable
120
by the Jupyter server extension system.
121
122
Returns:
123
List of extension metadata dictionaries with module and app info
124
"""
125
126
class JupyterHubSingleUser(ExtensionApp):
127
"""
128
Jupyter server extension for JupyterHub single-user integration.
129
130
Provides the extension implementation for Hub-authenticated
131
single-user servers.
132
"""
133
134
name: str = 'jupyterhub-singleuser'
135
description: str = 'JupyterHub single-user server extension'
136
137
def initialize_settings(self):
138
"""Initialize extension settings"""
139
140
def initialize_handlers(self):
141
"""Initialize request handlers"""
142
143
def initialize_templates(self):
144
"""Initialize Jinja2 templates"""
145
```
146
147
### Single-user Server Configuration
148
149
Configuration and startup utilities for single-user servers.
150
151
```python { .api }
152
class SingleUserNotebookApp(JupyterApp):
153
"""
154
Single-user notebook server application.
155
156
Main application class for JupyterHub single-user servers
157
with Hub authentication and integration.
158
"""
159
160
# Hub connection configuration
161
hub_host: str # JupyterHub host
162
hub_prefix: str # Hub URL prefix
163
hub_api_url: str # Hub API URL
164
165
# Authentication configuration
166
user: str # Username for this server
167
group: str # User group
168
cookie_name: str # Hub authentication cookie name
169
170
# Server configuration
171
base_url: str # Server base URL
172
default_url: str # Default redirect URL
173
174
# OAuth configuration (optional)
175
oauth_client_id: str # OAuth client ID
176
oauth_redirect_uri: str # OAuth redirect URI
177
178
def initialize(self, argv=None):
179
"""
180
Initialize single-user server.
181
182
Args:
183
argv: Command line arguments
184
"""
185
186
def start(self):
187
"""Start the single-user server"""
188
189
def make_singleuser_app(self):
190
"""Create single-user application with Hub integration"""
191
192
def main(argv=None) -> int:
193
"""
194
Main entry point for jupyterhub-singleuser command.
195
196
Args:
197
argv: Command line arguments
198
199
Returns:
200
Exit code (0 for success)
201
"""
202
```
203
204
### Hub API Integration
205
206
Tools for single-user servers to communicate with the JupyterHub API.
207
208
```python { .api }
209
class HubAPIClient:
210
"""
211
API client for single-user servers to communicate with Hub.
212
213
Provides methods for servers to report status and interact
214
with the central Hub.
215
"""
216
217
def __init__(self, hub_api_url: str, api_token: str):
218
"""
219
Initialize Hub API client.
220
221
Args:
222
hub_api_url: JupyterHub API base URL
223
api_token: API token for authentication
224
"""
225
226
async def get_user(self, username: str) -> Dict[str, Any]:
227
"""
228
Get user information from Hub.
229
230
Args:
231
username: Username to look up
232
233
Returns:
234
User information dictionary
235
"""
236
237
async def update_server_activity(self):
238
"""Update server activity timestamp in Hub"""
239
240
async def report_server_status(self, status: str):
241
"""
242
Report server status to Hub.
243
244
Args:
245
status: Server status ('running', 'ready', 'error', etc.)
246
"""
247
248
async def get_server_info(self, username: str, server_name: str = '') -> Dict[str, Any]:
249
"""
250
Get server information from Hub.
251
252
Args:
253
username: Server owner username
254
server_name: Named server (empty for default)
255
256
Returns:
257
Server information dictionary
258
"""
259
```
260
261
## Usage Examples
262
263
### Basic Single-user Server Setup
264
265
```python
266
# Create Hub-authenticated server application
267
from jupyter_server.serverapp import ServerApp
268
from jupyterhub.singleuser import make_singleuser_app
269
270
@make_singleuser_app
271
class MyHubServer(ServerApp):
272
"""Custom Hub-authenticated server"""
273
274
# Custom configuration
275
custom_setting = Unicode(
276
'default_value',
277
config=True,
278
help="Custom server setting"
279
)
280
281
def initialize_settings(self):
282
"""Initialize custom settings"""
283
super().initialize_settings()
284
285
# Add custom settings to web app
286
self.web_app.settings.update({
287
'custom_setting': self.custom_setting
288
})
289
290
# Start the server
291
if __name__ == '__main__':
292
MyHubServer.launch_instance()
293
```
294
295
### Custom Authentication Handler
296
297
```python
298
from jupyterhub.singleuser.mixins import HubAuthenticatedHandler
299
from tornado import web
300
301
class CustomAPIHandler(HubAuthenticatedHandler):
302
"""Custom API endpoint with Hub authentication"""
303
304
@web.authenticated
305
async def get(self):
306
"""GET endpoint with user authentication"""
307
user = self.current_user
308
if not user:
309
raise web.HTTPError(401, "Authentication required")
310
311
# Verify user matches server owner
312
if user['name'] != self.hub_user['name']:
313
raise web.HTTPError(403, "Access denied")
314
315
# Custom API logic
316
data = await self.get_user_data(user)
317
self.write({'data': data, 'user': user['name']})
318
319
async def get_user_data(self, user):
320
"""Get data specific to authenticated user"""
321
# Your custom data retrieval logic
322
return {'files': [], 'settings': {}}
323
324
# Register handler in server application
325
class CustomHubServer(SingleUserNotebookApp):
326
"""Server with custom API endpoints"""
327
328
def initialize_handlers(self):
329
"""Add custom handlers"""
330
super().initialize_handlers()
331
332
# Add custom API endpoint
333
self.web_app.add_handlers('.*', [
334
(r'/api/custom', CustomAPIHandler),
335
])
336
```
337
338
### Hub API Integration
339
340
```python
341
import os
342
from jupyterhub.singleuser import HubAPIClient
343
344
class IntegratedServer(SingleUserNotebookApp):
345
"""Server with Hub API integration"""
346
347
def __init__(self, **kwargs):
348
super().__init__(**kwargs)
349
350
# Initialize Hub API client
351
self.hub_client = HubAPIClient(
352
hub_api_url=os.environ['JUPYTERHUB_API_URL'],
353
api_token=os.environ['JUPYTERHUB_API_TOKEN']
354
)
355
356
async def initialize(self):
357
"""Initialize with Hub registration"""
358
await super().initialize()
359
360
# Report server startup to Hub
361
await self.hub_client.report_server_status('starting')
362
363
# Get user information from Hub
364
user_info = await self.hub_client.get_user(self.user)
365
self.log.info(f"Starting server for user: {user_info['name']}")
366
367
def start(self):
368
"""Start server and report to Hub"""
369
super().start()
370
371
# Report ready status
372
asyncio.create_task(self.hub_client.report_server_status('ready'))
373
374
async def periodic_status_update(self):
375
"""Periodic status updates to Hub"""
376
while True:
377
try:
378
await self.hub_client.update_server_activity()
379
await asyncio.sleep(300) # Update every 5 minutes
380
except Exception as e:
381
self.log.error(f"Failed to update Hub status: {e}")
382
await asyncio.sleep(60) # Retry after 1 minute
383
```
384
385
### Extension Integration
386
387
```python
388
from jupyterhub.singleuser.extension import JupyterHubSingleUser
389
from jupyter_server.extension.application import ExtensionApp
390
391
class CustomExtension(ExtensionApp):
392
"""Custom extension with Hub integration"""
393
394
name = 'my-hub-extension'
395
description = 'Custom JupyterHub extension'
396
397
def initialize_settings(self):
398
"""Initialize extension settings"""
399
# Get Hub authentication info
400
hub_user = self.serverapp.web_app.settings.get('hub_user')
401
if hub_user:
402
self.log.info(f"Extension initialized for user: {hub_user['name']}")
403
404
# Add custom settings
405
self.serverapp.web_app.settings.update({
406
'custom_extension_enabled': True
407
})
408
409
def initialize_handlers(self):
410
"""Initialize extension handlers"""
411
from .handlers import CustomHandler
412
413
self.handlers.extend([
414
(r'/my-extension/api/.*', CustomHandler),
415
])
416
417
# Register extension
418
def _jupyter_server_extension_paths():
419
"""Extension discovery function"""
420
return [{
421
'module': 'my_extension',
422
'app': CustomExtension
423
}]
424
```
425
426
### OAuth Integration
427
428
```python
429
class OAuthIntegratedServer(SingleUserNotebookApp):
430
"""Single-user server with OAuth integration"""
431
432
oauth_client_id = Unicode(
433
config=True,
434
help="OAuth client ID for Hub integration"
435
)
436
437
def initialize_settings(self):
438
"""Initialize OAuth settings"""
439
super().initialize_settings()
440
441
# Configure OAuth settings
442
if self.oauth_client_id:
443
self.web_app.settings.update({
444
'oauth_client_id': self.oauth_client_id,
445
'oauth_redirect_uri': f'{self.base_url}oauth-callback'
446
})
447
448
def initialize_handlers(self):
449
"""Add OAuth handlers"""
450
super().initialize_handlers()
451
452
# Add OAuth callback handler
453
self.web_app.add_handlers('.*', [
454
(r'/oauth-callback', OAuthCallbackHandler),
455
(r'/oauth-login', OAuthLoginHandler),
456
])
457
458
class OAuthCallbackHandler(HubAuthenticatedHandler):
459
"""Handle OAuth callback from Hub"""
460
461
async def get(self):
462
"""Process OAuth authorization callback"""
463
code = self.get_argument('code', None)
464
state = self.get_argument('state', None)
465
466
if not code:
467
raise web.HTTPError(400, "Missing authorization code")
468
469
# Exchange code for token with Hub
470
token_info = await self.exchange_oauth_code(code)
471
472
# Validate user and redirect
473
user = await self.validate_oauth_token(token_info['access_token'])
474
self.redirect(self.get_argument('next', '/'))
475
476
async def exchange_oauth_code(self, code):
477
"""Exchange OAuth code for access token"""
478
# Implementation depends on Hub OAuth configuration
479
pass
480
```
481
482
## Advanced Integration Patterns
483
484
### Multi-server User Environment
485
486
```python
487
class MultiServerManager:
488
"""Manage multiple servers for a single user"""
489
490
def __init__(self, hub_client, user_name):
491
self.hub_client = hub_client
492
self.user_name = user_name
493
self.servers = {}
494
495
async def get_user_servers(self):
496
"""Get all servers for user from Hub"""
497
user_info = await self.hub_client.get_user(self.user_name)
498
return user_info.get('servers', {})
499
500
async def start_named_server(self, server_name, server_options=None):
501
"""Start a named server for user"""
502
response = await self.hub_client.start_server(
503
username=self.user_name,
504
server_name=server_name,
505
options=server_options or {}
506
)
507
return response
508
509
async def stop_named_server(self, server_name):
510
"""Stop a named server"""
511
await self.hub_client.stop_server(
512
username=self.user_name,
513
server_name=server_name
514
)
515
```
516
517
### Server Health Monitoring
518
519
```python
520
class HealthMonitoredServer(SingleUserNotebookApp):
521
"""Server with health monitoring and auto-restart"""
522
523
health_check_interval = Integer(
524
60,
525
config=True,
526
help="Health check interval in seconds"
527
)
528
529
def start(self):
530
"""Start server with health monitoring"""
531
super().start()
532
533
# Start health monitoring task
534
asyncio.create_task(self.health_monitor())
535
536
async def health_monitor(self):
537
"""Monitor server health and report to Hub"""
538
while True:
539
try:
540
# Check server health
541
health_status = await self.check_health()
542
543
# Report to Hub
544
status = 'healthy' if health_status else 'unhealthy'
545
await self.hub_client.report_server_status(status)
546
547
# Auto-restart if unhealthy
548
if not health_status:
549
self.log.warning("Server unhealthy, requesting restart")
550
await self.request_restart()
551
552
await asyncio.sleep(self.health_check_interval)
553
except Exception as e:
554
self.log.error(f"Health monitoring error: {e}")
555
await asyncio.sleep(60)
556
557
async def check_health(self):
558
"""Check server health status"""
559
try:
560
# Custom health check logic
561
return True
562
except Exception:
563
return False
564
565
async def request_restart(self):
566
"""Request server restart from Hub"""
567
await self.hub_client.report_server_status('restart_requested')
568
```
569
570
### Shared Resource Access
571
572
```python
573
class SharedResourceServer(SingleUserNotebookApp):
574
"""Server with shared resource access controls"""
575
576
def initialize_settings(self):
577
"""Initialize with resource access controls"""
578
super().initialize_settings()
579
580
# Get user's resource permissions from Hub
581
user_scopes = self.hub_user.get('scopes', [])
582
583
# Configure resource access based on scopes
584
self.web_app.settings.update({
585
'shared_storage_access': 'shared-storage' in user_scopes,
586
'gpu_access': 'gpu-resources' in user_scopes,
587
'admin_access': 'admin' in user_scopes
588
})
589
590
def initialize_handlers(self):
591
"""Add resource access handlers"""
592
super().initialize_handlers()
593
594
# Add shared resource handlers
595
self.web_app.add_handlers('.*', [
596
(r'/shared-storage/.*', SharedStorageHandler),
597
(r'/gpu-status', GPUStatusHandler),
598
])
599
600
class SharedStorageHandler(HubAuthenticatedHandler):
601
"""Handler for shared storage access"""
602
603
@web.authenticated
604
async def get(self, path):
605
"""Access shared storage with permission check"""
606
if not self.settings.get('shared_storage_access'):
607
raise web.HTTPError(403, "Shared storage access denied")
608
609
# Shared storage access logic
610
pass
611
```