# How the Music Assistant Webserver Controller Handles Authentication and Middleware

> Learn how the Music Assistant webserver controller secures traffic with aiohttp middleware, token authentication, and route-level enforcement for HTTP and WebSocket connections.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: deep-dive
- Published: 2026-06-17

---

**The Music Assistant webserver controller implements a layered security model that combines an aiohttp middleware pipeline, a token-based authentication manager, and route-level enforcement to secure both HTTP and WebSocket traffic while supporting Home Assistant Ingress integration.**

The `music-assistant/server` repository provides a Python-based media server where the webserver controller ([`music_assistant/controllers/webserver/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/webserver/controller.py)) serves as the central entry point for all incoming HTTP and WebSocket connections. Understanding how this component orchestrates authentication and middleware processes is essential for developers extending the API or integrating with external authentication providers.

## Authentication Manager Architecture

The controller instantiates a dedicated **AuthenticationManager** during initialization within `WebserverController.__init__` (lines 101-114). This manager exposes core security methods including `authenticate_with_token`, `create_token`, `authenticate_with_credentials`, and various user-lookup helpers. The same authentication manager instance is shared across subsystems, including the remote-access subsystem and the SendSpin proxy, ensuring consistent credential validation throughout the application.

## Middleware Integration and Token Validation

When the server starts via the `setup` method, the controller registers the `mass` object as `app_state` (lines 55-58) and implicitly attaches the `auth_middleware` defined in [`helpers/auth_middleware.py`](https://github.com/music-assistant/server/blob/main/helpers/auth_middleware.py) to the aiohttp application. This middleware executes a three-stage validation pipeline for every incoming request:

### Ingress Request Handling

The middleware first checks `is_request_from_ingress` to detect Home Assistant Ingress traffic. Since Ingress already performed authentication at the reverse proxy layer, the middleware returns the request unchanged without additional token validation.

### Public Path Bypass

For non-Ingress traffic, the middleware compares the request path against an allowlist of unauthenticated routes. Static assets, `/info`, `/login`, `/setup`, and any sub-paths under `/auth/` proceed without requiring a token (lines 53-70).

### Bearer Token Authentication

All remaining routes undergo token-based authentication via `get_authenticated_user`. This function executes the following logic:

1. Checks the request context for a previously stored user (utilized by WebSocket handlers).
2. Handles Home Assistant Ingress user creation and account linking when special ingress headers are present.
3. Parses the `Authorization: Bearer <token>` header, validates the token against the authentication database, and explicitly rejects the special system user (`HOMEASSISTANT_SYSTEM_USER`) on non-Ingress traffic.

The authenticated user object (or `None`) is stored in the request under the key `authenticated_user` (lines 75-77) and simultaneously placed into a `ContextVar` named `current_user` via `set_current_user`, enabling downstream async functions to access the current user without passing the request object explicitly.

## Route-Level Security Enforcement

Individual route handlers enforce additional authorization policies. The JSON-RPC API method `_handle_jsonrpc_api_command` inspects handler attributes including `handler.authenticated` and `handler.required_role`. When authentication is required, it invokes `get_authenticated_user` and, upon success, sets the user in the context using `set_current_user`. Admin-only endpoints compare `user.role` against `UserRole.ADMIN` to restrict access.

Dedicated authentication endpoints (`/auth/login`, `/auth/logout`, `/auth/me`) interact directly with the authentication manager. For example, the login handler creates a new token only after `authenticate_with_credentials` successfully verifies the provided username and password (lines 97-122).

## WebSocket Authentication Flow

WebSocket connections are managed by `WebsocketClientHandler`. The protocol requires the first message to be an `"auth"` command. Upon receiving this command, the handler calls `self.webserver.auth.authenticate_with_token` and stores the resulting user, token, and token ID on the client instance. The connection rejects all subsequent messages until this authentication handshake completes successfully (lines 182-210).

## Token Revocation and Session Management

The controller maintains a registry of active WebSocket clients. When a logout event occurs (`_handle_auth_logout`), the controller removes the corresponding rows from the `auth_tokens` database table and immediately invokes `disconnect_websockets_for_token` to forcibly close all WebSocket connections associated with the revoked token (lines 99-108).

## Home Assistant Ingress Integration

Both HTTP and WebSocket flows recognize Ingress requests through `is_request_from_ingress`. In this mode:

- Users are automatically created or linked to existing Home Assistant accounts.
- The system user (`HOMEASSISTANT_SYSTEM_USER`) is explicitly prohibited from accessing the regular webserver interface.
- Trust is established based on the socket's bind address rather than header values alone, preventing header spoofing from untrusted networks (lines 11-18).

## Summary

- **The webserver controller** ([`music_assistant/controllers/webserver/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/webserver/controller.py)) initializes the authentication manager and wires middleware into the aiohttp application.
- **The auth middleware** ([`music_assistant/controllers/webserver/helpers/auth_middleware.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/webserver/helpers/auth_middleware.py)) implements a three-tier check: Ingress bypass, public path allowance, and Bearer token validation with user context storage.
- **Route handlers** enforce role-based access control by inspecting `authenticated_user` and `UserRole` attributes.
- **WebSocket connections** require an initial `"auth"` command handshake before processing subsequent messages.
- **Token revocation** triggers immediate disconnection of associated WebSocket sessions via `disconnect_websockets_for_token`.

## Frequently Asked Questions

### How does the middleware distinguish between Home Assistant Ingress and direct access?

The middleware calls `is_request_from_ingress`, which verifies the socket's bind address to confirm the connection originated from the internal Ingress proxy. When true, the middleware trusts the upstream authentication and auto-creates or links the Home Assistant user account without requiring a Music Assistant token.

### What happens if a WebSocket client sends messages before authenticating?

The `WebsocketClientHandler` rejects any message that arrives before a successful `"auth"` command. The handler stores the authenticated user on the client object only after `authenticate_with_token` validates the provided token, and all subsequent API calls reference this stored identity.

### Can the system user account access the webserver through regular HTTP requests?

No. The `get_authenticated_user` function explicitly blocks the `HOMEASSISTANT_SYSTEM_USER` from authenticating on non-Ingress traffic. This restriction prevents the system service account from being used as a login credential for the web interface.

### How are admin-only endpoints protected in the JSON-RPC API?

Handlers declare administrative requirements through the `required_role` attribute. The `_handle_jsonrpc_api_command` method checks if the authenticated user's `user.role` equals `UserRole.ADMIN` before executing the handler, returning a 403 Forbidden response if the role requirement is not met.