/
/
/
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.
300Frames are capped at the channel's
301`max_message_size`, which is the lower of our own 256 KiB ceiling and what the peer
302advertises in its SDP â and libdatachannel assumes only 64 KiB when it advertises nothing.
303
304**Adding a new label** is not backwards compatible by itself: servers from before the routing
305table mistake an unknown label for the API channel, which breaks the entire remote session
306instead of just the new feature. A client must therefore feature-detect on `schema_version`
307from `server_info` before opening one: `http_proxy` requires `API_SCHEMA_VERSION >= 49`. Bump
308`API_SCHEMA_VERSION` ([constants.py](../../constants.py)) when adding a label and gate the
309client on the new value.
310
311### ICE Servers (STUN/TURN)
312
313NAT traversal is critical for WebRTC connections. Music Assistant uses:
314
315- **STUN servers**: Servers for discovering public IP addresses and port mappings
316- **TURN servers**: Relay servers for cases where direct peer-to-peer connection fails
317
318**Basic Mode (Public STUN):**
319- `stun:stun.home-assistant.io:3478` (Home Assistant public STUN)
320- `stun:stun.l.google.com:19302` (Google public STUN)
321- `stun:stun1.l.google.com:19302` (Google public STUN backup)
322- `stun:stun.cloudflare.com:3478` (Cloudflare public STUN)
323
324Most connections succeed with public STUN servers alone, but they may fail in:
325- Symmetric NAT configurations
326- Corporate firewalls that block UDP
327- Networks with restrictive firewall policies
328
329**Optimized Mode (HA Cloud):**
330- STUN/TURN servers provided by Home Assistant Cloud
331- Includes TURN relay servers for guaranteed connectivity
332
333### Availability
334
335Remote access is available to all users:
336- **Basic Mode**: Always available, no subscription required
337- **Optimized Mode**: Requires active Home Assistant Cloud subscription
338
339### API Endpoints
340
341**`remote_access/info`** (WebSocket command):
342Returns remote access status:
343```json
344{
345 "enabled": true,
346 "running": true,
347 "connected": true,
348 "remote_id": "MA-K7G3-P2M4",
349 "using_ha_cloud": false,
350 "signaling_url": "wss://signaling.music-assistant.io/ws"
351}
352```
353
354**`remote_access/configure`** (WebSocket command, admin only):
355Enable or disable remote access:
356```json
357{
358 "enabled": true
359}
360```
361
362## Request Flow
363
364### HTTP Request Flow
365
366```
367HTTP Request â Webserver â Command Handler â Response
368 |
369 ââ get_authenticated_user()
370 ââ Ingress? â Auto-authenticate with HA headers
371 ââ Regular? â Validate Bearer token
372```
373
374### WebSocket Request Flow
375
376```
377WebSocket Connect â WebsocketClientHandler
378 |
379 ââ First command: auth â Validate token â Set user context
380 ââ Subsequent commands â Check auth/role â Execute â Respond
381```
382
383### Remote WebRTC Request Flow
384
385```
386Remote Client â WebRTC Data Channel â Gateway â Local WebSocket API
387 |
388 ââ Message forwarding (bidirectional)
389```
390
391## Security Considerations
392
393### Authentication
394
395- **Mandatory authentication**: All API access requires authentication (except Ingress)
396- **Secure token generation**: Uses `secrets.token_urlsafe(48)` for cryptographically secure tokens
397- **Password hashing**: bcrypt with user-specific salts
398- **Rate limiting**: Progressive delays on failed login attempts
399- **Token expiration**: Both short-lived (30 days sliding) and long-lived (10 years) tokens supported
400
401### Authorization
402
403- **Role-based access**: Admin vs User roles
404- **Command-level enforcement**: API commands can require specific roles
405- **Player/Provider filtering**: Users can be restricted to specific players/providers
406- **Token revocation**: Immediate WebSocket disconnect on token revocation
407
408### Network Security
409
410**Local Network:**
411- Webserver is unencrypted (HTTP) by design (runs on local network)
412- Users should use reverse proxy or VPN for external access
413- Never expose webserver directly to internet
414
415**Remote Access:**
416- End-to-end encryption via WebRTC (DTLS/SRTP)
417- Authentication required (same as local access)
418- Signaling server only routes encrypted signaling messages
419- Cannot decrypt or inspect user data
420
421### Data Protection
422
423- **Token storage**: Only hashed tokens stored in database
424- **Password storage**: bcrypt with user-specific salts
425- **Session cleanup**: Expired tokens automatically deleted
426- **User disable**: Immediate disconnect of all user sessions
427
428## Development Guide
429
430### Adding New Authentication Providers
431
4321. Create provider class inheriting from `LoginProvider` in [helpers/auth_providers.py](helpers/auth_providers.py)
4332. Implement required methods: `authenticate()`, `get_authorization_url()` (if OAuth), `handle_oauth_callback()` (if OAuth)
4343. Register provider in `AuthenticationManager._setup_login_providers()`
4354. Add provider configuration to webserver config entries if needed
436
437### Adding New API Endpoints
438
4391. Define route handler in [controller.py](controller.py) (for HTTP endpoints)
4402. Use `@api_command()` decorator for WebSocket commands (in respective controllers)
4413. Specify authentication requirements: `authenticated=True` and/or `required_scope=Scope.<SCOPE>`
4424. Optionally set `allow_impersonation=True` to let callers execute the command on behalf of
443 another user via the injected `user` argument (requires the `users.impersonate` scope
444 when targeting another user)
445
446### Testing Authentication
447
4481. **Local Testing**: Use `/setup` to create admin user, then `/auth/login` to get token
4492. **HTTP API Testing**: Use curl with `Authorization: Bearer <token>` header
4503. **WebSocket Testing**: Connect to `/ws` and send auth command with token
4514. **Role Testing**: Create users with different roles and test access restrictions
452
453### Common Patterns
454
455**Getting current user in command handler:**
456```python
457from music_assistant.controllers.webserver.helpers.auth_middleware import get_current_user
458
459@api_command("my_command")
460async def my_command():
461 user = get_current_user()
462 if not user:
463 raise AuthenticationRequired("Not authenticated")
464 # ... use user ...
465```
466
467**Getting current token (for revocation):**
468```python
469from music_assistant.controllers.webserver.helpers.auth_middleware import get_current_token
470
471@api_command("my_command")
472async def my_command():
473 token = get_current_token()
474 # ... use token ...
475```
476
477**Requiring a scope:**
478```python
479from music_assistant_models.auth import Scope
480
481@api_command("admin_only_command", required_scope=Scope.CONFIG_CORE_WRITE)
482async def admin_command():
483 # Only users whose role grants the config.core.write scope can call this
484 pass
485```
486
487Scopes are granted to users through their role, see `ROLE_SCOPES` in
488[helpers/auth_middleware.py](helpers/auth_middleware.py) for the builtin role definitions.
489
490### Database Migrations
491
492When modifying the auth database schema:
4931. Increment `DB_SCHEMA_VERSION` in [auth.py](auth.py)
4942. Add migration logic to `_migrate_database()` method
4953. Test migration from previous version
4964. Consider backwards compatibility
497
498### Testing Remote Access
499
5001. **Enable Remote Access**: Toggle remote access in settings UI or via API
5012. **Verify Remote ID**: Check webserver config for generated Remote ID
5023. **Test Gateway**: Check logs for "Starting remote access in basic/optimized mode" message
5034. **Test Connection**: Use PWA with Remote ID to connect externally
5045. **Monitor Sessions**: Check `remote_access/info` command for status and mode
5056. **Test Mode Switching**: Enable/disable HA Cloud and verify automatic mode switching
506
507## File Structure
508
509```
510webserver/
511âââ __init__.py # Module exports
512âââ controller.py # Main webserver controller
513âââ auth.py # Authentication manager
514âââ websocket_client.py # WebSocket client handler
515âââ api_docs.py # API documentation generator
516âââ README.md # This file
517âââ helpers/
518â âââ auth_middleware.py # HTTP/WebSocket auth helpers
519â âââ auth_providers.py # Authentication providers
520âââ remote_access/
521 âââ __init__.py # Remote access manager
522 âââ gateway.py # WebRTC gateway implementation
523```
524
525## Additional Resources
526
527- [API Documentation](http://localhost:8095/api-docs) - Auto-generated API docs
528- [Commands Reference](http://localhost:8095/api-docs/commands) - List of all API commands
529- [Schemas Reference](http://localhost:8095/api-docs/schemas) - Data model documentation
530- [Swagger UI](http://localhost:8095/api-docs/swagger) - Interactive API explorer
531
532## Contributing
533
534When contributing to the webserver/auth system:
5351. Follow the existing patterns for consistency
5362. Add comprehensive docstrings with Sphinx-style parameter documentation
5373. Update this README if adding significant new features
5384. Test authentication flows thoroughly
5395. Consider security implications of all changes
5406. The API documentation will be auto updated if adding new commands (based on docstrings and type hints)
541
542---
543
544*This architecture document is maintained alongside the code and should be updated when significant changes are made to the provider's design or functionality.*
545