How Music Assistant Server Handles API Authentication: JWT Tokens, SHA-256 Hashing, and Middleware Validation

The Music Assistant webserver authenticates API requests using a hybrid JWT and legacy hash token system, with tokens stored as SHA-256 hashes in SQLite and validated through request middleware that supports Bearer tokens and Home Assistant Ingress headers.

The authentication flow in the music-assistant/server repository secures REST and WebSocket endpoints by issuing signed JSON Web Tokens (JWTs) while maintaining a revocation database in auth.db. This guide breaks down the complete authentication mechanism—from token creation in auth.py to request gating in auth_middleware.py—based on the current source code implementation.

Token Creation and JWT Generation

When a user logs in via the WebSocket auth/login command or an admin creates a token manually, the AuthenticationManager.create_token() method in music_assistant/controllers/webserver/auth.py executes a three-step process:

  1. Builds the JWT payload containing the user UUID (sub), a unique token ID (jti), expiration timestamp (exp), and a boolean flag (is_long_lived) that determines whether the token auto-renews
  2. Signs the payload using a secret key retrieved via AuthenticationManager._get_or_create_jwt_secret() (lines 304-324)
  3. Stores a SHA-256 hash of the raw JWT in the auth_tokens table, enabling revocation without persisting the clear-text token

# music_assistant/controllers/webserver/auth.py

token = self.jwt_helper.encode_token(
    user=user,
    token_id=token_id,
    token_name=name,
    expires_at=expires_at,
    is_long_lived=is_long_lived,
)
token_hash = hashlib.sha256(token.encode()).hexdigest()
await self.database.insert("auth_tokens", {
    "token_id": token_id,
    "user_id": user.user_id,
    "token_hash": token_hash,
    "name": name,
    "created_at": created_at.isoformat(),
    "expires_at": expires_at.isoformat(),
    "is_long_lived": 1 if is_long_lived else 0,
})

Short-lived tokens expire after 30 days (TOKEN_SHORT_LIVED_EXPIRATION) and support sliding-window renewal, while long-lived tokens expire after 10 years (TOKEN_LONG_LIVED_EXPIRATION) and do not auto-refresh.

Token Verification and Legacy Fallback

Incoming API requests carry tokens in the Authorization: Bearer <token> header. The AuthenticationManager.authenticate_with_token() method (lines 99-150) validates these through a dual-path verification system:

Primary JWT Path: Decodes the token using self.jwt_helper.decode_token(token, verify_exp=True), extracts the jti and sub claims, and validates against the database row.

Legacy Hash Fallback: If JWT decoding fails with InvalidTokenError, the server hashes the raw token string and performs a direct lookup on the auth_tokens.token_hash column.

For short-lived tokens, successful verification triggers a sliding-window expiration update that extends the token validity by another 30 days from the current time:


# music_assistant/controllers/webserver/auth.py

try:
    payload = self.jwt_helper.decode_token(token, verify_exp=True)
    token_id = payload.get("jti")
    user_id = payload.get("sub")
    # Database lookup, expiration check, and sliding-window update

    return await self.get_user(user_id)
except pyjwt.InvalidTokenError:
    # Legacy hash verification path

    token_hash = hashlib.sha256(token.encode()).hexdigest()
    token_row = await self.database.get_row("auth_tokens", {"token_hash": token_hash})
    return await self.get_user(token_row["user_id"])

If verification fails or the token is revoked (row missing from database), the method returns None, triggering a 401 Unauthorized response at the middleware layer.

Request Authentication Middleware

All HTTP endpoints pass through auth_middleware.auth_middleware, which gates requests based on path and authentication status. The middleware delegates token extraction to get_authenticated_user() in music_assistant/controllers/webserver/helpers/auth_middleware.py.

Token Extraction Logic:

  • Checks for Home Assistant Ingress headers first (for HA-managed installations)
  • Falls back to Authorization header parsing for Bearer tokens
  • Validates token format and calls AuthenticationManager.authenticate_with_token()

# music_assistant/controllers/webserver/helpers/auth_middleware.py

auth_header = request.headers.get("Authorization")
if not auth_header:
    return None

parts = auth_header.split(" ", 1)
if len(parts) != 2 or parts[0].lower() != "bearer":
    return None

token = parts[1]
user = await mass.webserver.auth.authenticate_with_token(token)

Upon successful authentication, the middleware injects the User object into the request context as request["authenticated_user"] and sets a ContextVar (current_user) for downstream access via get_current_user().

Authorization Helpers:

  • require_authentication() raises 401 Unauthorized for unauthenticated requests
  • require_admin() validates the UserRole.ADMIN role and raises 403 Forbidden for non-admin users

Public paths such as /info, /login, and static assets bypass authentication checks entirely.

Practical API Usage Examples

Obtain an Access Token (Login)

curl -X POST http://localhost:8095/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"s3cureP@ss"}'

Successful response:

{
  "success": true,
  "access_token": "<jwt>",
  "user": {
    "user_id": "Z3u3-...",
    "username": "alice",
    "display_name": "Alice",
    "role": "user"
  }
}

Access a Protected Endpoint

curl http://localhost:8095/api/auth/users \
  -H "Authorization: Bearer <jwt>"

Returns the user list on valid tokens, or 401 Unauthorized on invalid or expired tokens.

Revoke a Token

curl -X POST http://localhost:8095/api/auth/token/revoke \
  -H "Authorization: Bearer <jwt>" \
  -d '{"token_id":"abc123"}'

Only token owners or admins can revoke tokens. Revocation deletes the row from auth_tokens and disconnects active WebSocket connections using that token.

Summary

  • Token Creation: AuthenticationManager.create_token() in auth.py generates JWTs with 30-day or 10-year expiration, storing SHA-256 hashes in the SQLite auth.db
  • Verification: authenticate_with_token() validates JWT signatures with fallback to legacy hash lookups, implementing sliding-window renewal for short-lived tokens
  • Middleware: auth_middleware.py extracts Bearer tokens or Home Assistant Ingress headers, injecting authenticated users into request context
  • Security: Revocation is enforced by database row deletion; role-based access control (ADMIN vs USER) returns 403 Forbidden for unauthorized operations

Frequently Asked Questions

How does Music Assistant store authentication tokens securely?

The server never stores the raw JWT string. Instead, AuthenticationManager.create_token() calculates a SHA-256 hash of the token and persists only the hash, along with metadata (token ID, user ID, expiration), in the auth_tokens table of the SQLite auth.db database. This allows validation and revocation without exposing the actual token values in storage.

What is the difference between short-lived and long-lived tokens?

Short-lived tokens expire after 30 days (TOKEN_SHORT_LIVED_EXPIRATION) and automatically extend their expiration by another 30 days each time they are used successfully. Long-lived tokens expire after 10 years (TOKEN_LONG_LIVED_EXPIRATION) and do not auto-renew, making them suitable for external integrations that require stable credentials.

How does the middleware handle authentication for Home Assistant installations?

The get_authenticated_user() function in auth_middleware.py first checks for Home Assistant Ingress headers before falling back to standard Bearer token extraction. This allows the Music Assistant server to integrate seamlessly with Home Assistant's authentication proxy when running as an add-on, while still supporting standalone Bearer token authentication for direct API access.

Can legacy hash tokens still be used for authentication?

Yes. The authenticate_with_token() method maintains backward compatibility by catching InvalidTokenError from JWT decoding and falling back to a direct SHA-256 hash lookup in the database. This ensures that older tokens or specific integration patterns that rely on hash-based validation continue to function while the system migrates toward full JWT usage.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →