/
/
/
1# Webserver and Authentication Architecture
2
3This document provides a comprehensive overview of the Music Assistant webserver architecture, authentication system, and remote access capabilities.
4
5## Table of Contents
6
7- [Overview](#overview)
8- [Core Components](#core-components)
9- [Authentication System](#authentication-system)
10- [Remote Access (WebRTC)](#remote-access-webrtc)
11- [Request Flow](#request-flow)
12- [Security Considerations](#security-considerations)
13- [Development Guide](#development-guide)
14
15## Overview
16
17The Music Assistant webserver is a core controller that provides:
18- WebSocket-based real-time API for bidirectional communication
19- HTTP/JSON-RPC API for simple request-response interactions
20- User authentication and authorization system
21- Frontend hosting (Vue-based PWA)
22- Remote access via WebRTC for external connectivity
23- Home Assistant integration via Ingress
24
25The webserver runs on port `8095` by default and can be configured via the webserver controller settings.
26
27## Core Components
28
29### 1. WebserverController ([controller.py](controller.py))
30
31The main orchestrator that manages:
32- HTTP server setup and lifecycle
33- Route registration (static files, API endpoints, auth endpoints)
34- WebSocket client management
35- Authentication manager initialization
36- Remote access manager initialization
37- Home Assistant Supervisor announcement (when running as add-on)
38
39**Key responsibilities:**
40- Serves the frontend application (PWA)
41- Hosts the WebSocket API endpoint (`/ws`)
42- Provides HTTP/JSON-RPC API endpoint (`/api`)
43- Manages authentication routes (`/login`, `/auth/*`, `/setup`)
44- Serves API documentation (`/api-docs`)
45- Handles image proxy and audio preview endpoints
46
47### 2. AuthenticationManager ([auth.py](auth.py))
48
49Handles all authentication and user management:
50
51**Database Schema:**
52- `users` - User accounts with roles (admin/user)
53- `user_auth_providers` - Links users to authentication providers (many-to-many)
54- `auth_tokens` - Access tokens with expiration tracking
55- `settings` - Schema version and configuration
56
57**Authentication Providers:**
58- **Built-in Provider** - Username/password authentication with bcrypt hashing
59- **Home Assistant OAuth** - OAuth2 flow for Home Assistant users (auto-enabled when HA provider is configured)
60
61**Token Types:**
62- **Short-lived tokens**: Auto-renewing on use, 30-day sliding expiration window (for user sessions)
63- **Long-lived tokens**: No auto-renewal, 10-year expiration (for integrations/API access)
64
65**Security Features:**
66- Rate limiting on login attempts (progressive delays)
67- Password hashing with bcrypt and user- and server specific salts
68- Secure token generation with secrets.token_urlsafe()
69- WebSocket disconnect on token revocation
70- Session management and cleanup
71
72**User Roles:**
73- `ADMIN` - Full access to all commands and settings
74- `USER` - Standard access (configurable via player/provider filters)
75- `GUEST` - Read-only library access plus player/queue control
76- `SERVICE` - Standard access plus player config, reading user accounts and impersonation
77 (used by the Home Assistant integration)
78
79### 3. RemoteAccessManager ([remote_access/](remote_access/))
80
81Manages WebRTC-based remote access for external connectivity:
82
83**Architecture:**
84- **Signaling Server**: Cloud-based WebSocket server for WebRTC signaling (hosted at `wss://signaling.music-assistant.io/ws`)
85- **WebRTC Gateway**: Local component that bridges WebRTC data channels to the WebSocket API
86- **Remote ID**: Unique identifier (format: `MA-XXXX-XXXX`) for connecting to specific instances
87
88**How it works:**
891. Remote access can be enabled regardless of Home Assistant Cloud subscription
902. A unique Remote ID is generated and stored in config
913. The gateway connects to the signaling server and registers with the Remote ID
924. Remote clients (PWA or mobile apps) connect via WebRTC using the Remote ID
935. Data channel messages are bridged to/from the local WebSocket API
94
95**Connection Modes:**
96
97- **Basic Mode** (default, no HA Cloud required):
98 - Uses public STUN servers (Home Assistant, Google, Cloudflare)
99 - Works in most network configurations
100 - May not work behind complex NAT setups or corporate firewalls
101 - Free for all users
102
103- **Optimized Mode** (with HA Cloud subscription):
104 - Uses Home Assistant Cloud STUN/TURN servers
105 - Reliable connections in all network configurations
106 - TURN relay servers ensure connectivity even in restrictive networks
107 - Requires active Home Assistant Cloud subscription
108
109**Key features:**
110- Automatic reconnection on signaling server disconnect
111- Multiple concurrent WebRTC sessions supported
112- No port forwarding required
113- End-to-end encryption via WebRTC (DTLS-SRTP)
114- Automatic mode switching when HA Cloud status changes
115
116### 4. WebSocket Client Handler ([websocket_client.py](websocket_client.py))
117
118Manages individual WebSocket connections:
119- Authentication enforcement (auth or login command must be first)
120- Command routing and response handling
121- Event subscription and broadcasting
122- Connection lifecycle management
123- Token validation and user context
124
125### 5. Authentication Helpers
126
127**Helpers ([helpers/auth_middleware.py](helpers/auth_middleware.py)):**
128- Request authentication for HTTP endpoints, called per handler (there is no aiohttp middleware)
129- User context management (thread-local storage)
130- Ingress detection (Home Assistant add-on)
131- Token extraction from Authorization header
132
133**Providers ([helpers/auth_providers.py](helpers/auth_providers.py)):**
134- Base classes for authentication providers
135- Built-in username/password provider
136- Home Assistant OAuth provider
137- Rate limiting implementation
138
139## Authentication System
140
141### First-Time Setup Flow
142
1431. **Initial State**: No users exist
1442. **Setup Required**: User is redirected to `/setup`
1453. **Admin Creation**: User creates the first admin account with username/password
1464. **Setup completes** User gets redirected to the frontend
1475. **Onboarding wizard** The frontend shows the onboarding wizard if it detects 'onboard_done' is False
1484. **Onboarding Complete**: User completes onboarding and the `onboard_done` flag is set to `true`
149
150### First-Time Setup Flow when HA Ingress is used
151
1521. **Initial State**: No users exist
1532. **Auto user creation**: User is auto created based on HA user
1544. **Setup completes** User gets redirected to the frontend
1555. **Onboarding wizard** The frontend shows the onboarding wizard if it detects 'onboard_done' is False
1564. **Onboarding Complete**: User completes onboarding and the `onboard_done` flag is set to `true`
157
158### Login Flow (Standard)
159
1601. **Client Request**: POST to `/auth/login` with credentials
1612. **Provider Authentication**: Credentials validated by authentication provider
1623. **Token Generation**: Short-lived token created for the user
1634. **Response**: Token and user info returned to frontend
1645. **Subsequent Requests**: Token included in Authorization header or WebSocket auth command
165
166### Login Flow (Home Assistant OAuth)
167
1681. **Initiate OAuth**: GET `/auth/authorize?provider_id=homeassistant&return_url=...`
1692. **Redirect to HA**: User is redirected to Home Assistant OAuth consent page
1703. **OAuth Callback**: HA redirects back to `/auth/callback` with code and state
1714. **Token Exchange**: Code exchanged for HA access token
1725. **User Lookup/Creation**: User found or created with HA provider link
1736. **Token Generation**: MA token created and returned via redirect with `code` parameter
1747. **Client Handling**: Client extracts token from URL and stores it
175
176### Remote Client OAuth Flow
177
178For remote clients (PWA over WebRTC), OAuth requires special handling since redirect URLs can't point to localhost:
179
1801. **Request Session**: Remote client calls `auth/authorization_url` with `for_remote_client=true`
1812. **Session Created**: Server creates a pending OAuth session and returns session_id and auth URL
1823. **User Opens Browser**: Client opens auth URL in system browser
1834. **OAuth Flow**: User completes OAuth in browser
1845. **Token Stored**: Server stores token in pending session (using special return URL format)
1856. **Polling**: Client polls `auth/oauth_status` with session_id
1867. **Token Retrieved**: Once complete, client receives token and can authenticate
187
188### Ingress Authentication (Home Assistant Add-on)
189
190When running as a Home Assistant add-on:
191- A dedicated webserver TCP site is hosted (on port 8094) bound to the internal HA docker network only
192- Ingress requests include HA user headers (`X-Remote-User-ID`, `X-Remote-User-Name`)
193- Users are auto-created on first access
194- No password required (authentication handled by HA)
195- System user created for HA integration communication
196
197### WebSocket Authentication
198
1991. **Connection Established**: Client connects to `/ws`
2002. **Auth Command Required**: First command must be `auth` with token
2013. **Token Validation**: Token validated and user context set
2024. **Authenticated Session**: All subsequent commands executed in user context
2035. **Auto-Disconnect**: Connection closed on token revocation or user disable
204
205## Remote Access (WebRTC)
206
207### Architecture Overview
208
209Remote access enables users to connect to their Music Assistant instance from anywhere without port forwarding or VPN:
210
211```
212[Remote Client (PWA or app)]
213 |
214 | WebRTC Data Channel
215 v
216[Signaling Server] ââ [WebRTC Gateway]
217 |
218 | WebSocket
219 v
220 [Local WebSocket API]
221```
222
223### Components
224
225**Signaling Server** (`wss://signaling.music-assistant.io/ws`):
226- Cloud-based WebSocket server for WebRTC signaling
227- Handles SDP offer/answer exchange
228- Routes ICE candidates between peers
229- Maintains Remote ID registry
230
231**WebRTC Gateway** ([remote_access/gateway.py](remote_access/gateway.py)):
232- Runs locally as part of the webserver controller
233- Connects to signaling server and registers Remote ID
234- Accepts incoming WebRTC connections from remote clients
235- Bridges WebRTC data channel messages to local WebSocket API
236- Handles multiple concurrent sessions
237
238**Remote ID**:
239- Format: `MA-XXXX-XXXX` (e.g., `MA-K7G3-P2M4`)
240- Uniquely identifies a Music Assistant instance
241- Generated once and stored in controller config
242- Used by remote clients to connect to specific instance
243
244### Connection Flow
245
2461. **Initialization**:
247 - Remote access is enabled by user in settings
248 - Remote ID generated/retrieved from config
249 - HA Cloud status checked (determines mode)
250 - Gateway connects to signaling server with appropriate ICE servers
251 - Remote ID registered with signaling server
252
2532. **Remote Client Connection**:
254 - User opens PWA (https://app.music-assistant.io) and enters Remote ID
255 - PWA creates WebRTC peer connection
256 - PWA sends SDP offer via signaling server
257 - Gateway receives offer and creates peer connection
258 - Gateway sends SDP answer via signaling server
259 - ICE candidates exchanged for NAT traversal
260 - WebRTC data channel established
261
2623. **Message Bridging**:
263 - Remote client sends WebSocket-format messages over data channel
264 - Gateway forwards messages to local WebSocket API
265 - Responses and events sent back through data channel
266 - Authentication and authorization work identically to local WebSocket
267
268### Data Channels
269
270A single remote session multiplexes several WebRTC data channels over one peer connection.
271The gateway routes each incoming channel by its label through a label -> handler table, with
272two kinds of handlers:
273
274- **Bridged**: the channel is pumped both ways to a local WebSocket
275 - `sendspin`: the built-in Sendspin server (web player)
276 - `live_announcement`: the live announcement route on the local webserver
277- **Served in-process**: handled by the gateway itself, without a local WebSocket
278 - `http_proxy`: proxied HTTP requests (album art and other assets)
279
280When one of these channels or its local WebSocket closes, only that channel is torn down and
281the session stays up.
282
283The client's own API channel has no fixed label: the **first** channel with a label the server
284does not recognise becomes the API channel (the frontend labels it `ma-api`) and is bridged to
285`/ws`. Any **later** unrecognised label is refused, since taking it for a second API channel
286would replace the live bridge and break the session. The API channel shares its lifetime with
287the session: when it or its local WebSocket closes, the whole session is torn down.
288
289Proxied HTTP requests are answered on the channel they arrived on. That is what keeps older
290clients working: they send `http-proxy-request` over `ma-api` and get the response back there,
291so the gateway needs no version negotiation of its own.
292
293The reply is framed to suit that channel. On `ma-api` it is one JSON message with the body
294hex-encoded, which costs about 2.7x the image once the oversized-message chunking below is
295applied on top. `http_proxy` carries nothing else, so there the reply is a JSON header
296(`type`, `id`, `status`, `headers`, `size`) followed by the body as raw binary messages â the
297image costs its own size and no more. Those binary messages carry no request id, so the
298gateway holds the channel for a whole reply: replies go out one at a time rather than
299interleaving, which a channel that sends one message at a time would do anyway.
300
301`ma-api` and `http_proxy` size their bulk frames to the channel's `max_message_size`, the lower of
302our own 256 KiB ceiling and what the peer advertises in its SDP â and libdatachannel assumes
303only 64 KiB when it advertises nothing. On `http_proxy` that bounds the binary body frames. On
304`ma-api` any message over 64 KiB â or over the cap, whichever is lower â is split into
305`__chunk__` frames (`id`, `seq`, `count`, `b64`) the client reassembles by group id. A client
306therefore has to expect chunking well before the cap: pieces are 64 KiB by preference, sized
307down only when the cap cannot fit that much base64 plus the frame's JSON envelope.
308
309**Adding a new label** is not backwards compatible by itself: servers from before the routing
310table mistake an unknown label for the API channel, which breaks the entire remote session
311instead of just the new feature. A client must therefore feature-detect on `schema_version`
312from `server_info` before opening one: `http_proxy` requires `API_SCHEMA_VERSION >= 49`. Bump
313`API_SCHEMA_VERSION` ([constants.py](../../constants.py)) when adding a label and gate the
314client on the new value.
315
316### ICE Servers (STUN/TURN)
317
318NAT traversal is critical for WebRTC connections. Music Assistant uses:
319
320- **STUN servers**: Servers for discovering public IP addresses and port mappings
321- **TURN servers**: Relay servers for cases where direct peer-to-peer connection fails
322
323**Basic Mode (Public STUN):**
324- `stun:stun.home-assistant.io:3478` (Home Assistant public STUN)
325- `stun:stun.l.google.com:19302` (Google public STUN)
326- `stun:stun1.l.google.com:19302` (Google public STUN backup)
327- `stun:stun.cloudflare.com:3478` (Cloudflare public STUN)
328
329Most connections succeed with public STUN servers alone, but they may fail in:
330- Symmetric NAT configurations
331- Corporate firewalls that block UDP
332- Networks with restrictive firewall policies
333
334**Optimized Mode (HA Cloud):**
335- STUN/TURN servers provided by Home Assistant Cloud
336- Includes TURN relay servers for guaranteed connectivity
337
338### Availability
339
340Remote access is available to all users:
341- **Basic Mode**: Always available, no subscription required
342- **Optimized Mode**: Requires active Home Assistant Cloud subscription
343
344### API Endpoints
345
346**`remote_access/info`** (WebSocket command):
347Returns remote access status:
348```json
349{
350 "enabled": true,
351 "running": true,
352 "connected": true,
353 "remote_id": "MA-K7G3-P2M4",
354 "using_ha_cloud": false,
355 "signaling_url": "wss://signaling.music-assistant.io/ws"
356}
357```
358
359**`remote_access/configure`** (WebSocket command, admin only):
360Enable or disable remote access:
361```json
362{
363 "enabled": true
364}
365```
366
367## Request Flow
368
369### HTTP Request Flow
370
371```
372HTTP Request â Webserver â Command Handler â Response
373 |
374 ââ get_authenticated_user()
375 ââ Ingress? â Auto-authenticate with HA headers
376 ââ Regular? â Validate Bearer token
377```
378
379### WebSocket Request Flow
380
381```
382WebSocket Connect â WebsocketClientHandler
383 |
384 ââ First command: auth â Validate token â Set user context
385 ââ Subsequent commands â Check auth/role â Execute â Respond
386```
387
388### Remote WebRTC Request Flow
389
390```
391Remote Client â WebRTC Data Channel â Gateway â Local WebSocket API
392 |
393 ââ Message forwarding (bidirectional)
394```
395
396## Security Considerations
397
398### Authentication
399
400- **Mandatory authentication**: All API access requires authentication (except Ingress)
401- **Secure token generation**: Uses `secrets.token_urlsafe(48)` for cryptographically secure tokens
402- **Password hashing**: bcrypt with user-specific salts
403- **Rate limiting**: Progressive delays on failed login attempts
404- **Token expiration**: Both short-lived (30 days sliding) and long-lived (10 years) tokens supported
405
406### Authorization
407
408- **Role-based access**: Admin vs User roles
409- **Command-level enforcement**: API commands can require specific roles
410- **Player/Provider filtering**: Users can be restricted to specific players/providers
411- **Token revocation**: Immediate WebSocket disconnect on token revocation
412
413### Network Security
414
415**Local Network:**
416- Webserver is unencrypted (HTTP) by design (runs on local network)
417- Users should use reverse proxy or VPN for external access
418- Never expose webserver directly to internet
419
420**Remote Access:**
421- End-to-end encryption via WebRTC (DTLS/SRTP)
422- Authentication required (same as local access)
423- Signaling server only routes encrypted signaling messages
424- Cannot decrypt or inspect user data
425
426### Data Protection
427
428- **Token storage**: Only hashed tokens stored in database
429- **Password storage**: bcrypt with user-specific salts
430- **Session cleanup**: Expired tokens automatically deleted
431- **User disable**: Immediate disconnect of all user sessions
432
433## Development Guide
434
435### Adding New Authentication Providers
436
4371. Create provider class inheriting from `LoginProvider` in [helpers/auth_providers.py](helpers/auth_providers.py)
4382. Implement required methods: `authenticate()`, `get_authorization_url()` (if OAuth), `handle_oauth_callback()` (if OAuth)
4393. Register provider in `AuthenticationManager._setup_login_providers()`
4404. Add provider configuration to webserver config entries if needed
441
442### Adding New API Endpoints
443
4441. Define route handler in [controller.py](controller.py) (for HTTP endpoints)
4452. Use `@api_command()` decorator for WebSocket commands (in respective controllers)
4463. Specify authentication requirements: `authenticated=True` and/or `required_scope=Scope.<SCOPE>`
4474. Optionally set `allow_impersonation=True` to let callers execute the command on behalf of
448 another user via the injected `user` argument (requires the `users.impersonate` scope
449 when targeting another user)
450
451### Testing Authentication
452
4531. **Local Testing**: Use `/setup` to create admin user, then `/auth/login` to get token
4542. **HTTP API Testing**: Use curl with `Authorization: Bearer <token>` header
4553. **WebSocket Testing**: Connect to `/ws` and send auth command with token
4564. **Role Testing**: Create users with different roles and test access restrictions
457
458### Common Patterns
459
460**Getting current user in command handler:**
461```python
462from music_assistant.controllers.webserver.helpers.auth_middleware import get_current_user
463
464@api_command("my_command")
465async def my_command():
466 user = get_current_user()
467 if not user:
468 raise AuthenticationRequired("Not authenticated")
469 # ... use user ...
470```
471
472**Getting current token (for revocation):**
473```python
474from music_assistant.controllers.webserver.helpers.auth_middleware import get_current_token
475
476@api_command("my_command")
477async def my_command():
478 token = get_current_token()
479 # ... use token ...
480```
481
482**Requiring a scope:**
483```python
484from music_assistant_models.auth import Scope
485
486@api_command("admin_only_command", required_scope=Scope.CONFIG_CORE_WRITE)
487async def admin_command():
488 # Only users whose role grants the config.core.write scope can call this
489 pass
490```
491
492Scopes are granted to users through their role, see `ROLE_SCOPES` in
493[helpers/auth_middleware.py](helpers/auth_middleware.py) for the builtin role definitions.
494
495### Database Migrations
496
497When modifying the auth database schema:
4981. Increment `DB_SCHEMA_VERSION` in [auth.py](auth.py)
4992. Add migration logic to `_migrate_database()` method
5003. Test migration from previous version
5014. Consider backwards compatibility
502
503### Testing Remote Access
504
5051. **Enable Remote Access**: Toggle remote access in settings UI or via API
5062. **Verify Remote ID**: Check webserver config for generated Remote ID
5073. **Test Gateway**: Check logs for "Starting remote access in basic/optimized mode" message
5084. **Test Connection**: Use PWA with Remote ID to connect externally
5095. **Monitor Sessions**: Check `remote_access/info` command for status and mode
5106. **Test Mode Switching**: Enable/disable HA Cloud and verify automatic mode switching
511
512## File Structure
513
514```
515webserver/
516âââ __init__.py # Module exports
517âââ controller.py # Main webserver controller
518âââ auth.py # Authentication manager
519âââ websocket_client.py # WebSocket client handler
520âââ api_docs.py # API documentation generator
521âââ README.md # This file
522âââ helpers/
523â âââ auth_middleware.py # HTTP/WebSocket auth helpers
524â âââ auth_providers.py # Authentication providers
525âââ remote_access/
526 âââ __init__.py # Remote access manager
527 âââ gateway.py # WebRTC gateway implementation
528```
529
530## Additional Resources
531
532- [API Documentation](http://localhost:8095/api-docs) - Auto-generated API docs
533- [Commands Reference](http://localhost:8095/api-docs/commands) - List of all API commands
534- [Schemas Reference](http://localhost:8095/api-docs/schemas) - Data model documentation
535- [Swagger UI](http://localhost:8095/api-docs/swagger) - Interactive API explorer
536
537## Contributing
538
539When contributing to the webserver/auth system:
5401. Follow the existing patterns for consistency
5412. Add comprehensive docstrings with Sphinx-style parameter documentation
5423. Update this README if adding significant new features
5434. Test authentication flows thoroughly
5445. Consider security implications of all changes
5456. The API documentation will be auto updated if adding new commands (based on docstrings and type hints)
546
547---
548
549*This architecture document is maintained alongside the code and should be updated when significant changes are made to the provider's design or functionality.*
550