Add JWT-based authentication with backward compatibility (#2891)
authorZtripez <ztripez@vonmatern.org>
Wed, 28 Jan 2026 07:58:34 +0000 (08:58 +0100)
committerGitHub <noreply@github.com>
Wed, 28 Jan 2026 07:58:34 +0000 (08:58 +0100)
commitaccd29007db478422f4b12f0a47d1c0eb07d7d6e
tree76a489d32b34471f2b4470773329720ea819ea0c
parent0c300992d8fd2ba340ddf8da71196e5962cc4281
Add JWT-based authentication with backward compatibility (#2891)

* Add JWT-based authentication with backward compatibility

Migrates from hash-based tokens to JWT (JSON Web Tokens) while maintaining
full backward compatibility with existing tokens. This enables stateless
authentication with embedded user claims for better integration with
external systems and OAuth2/OIDC compliance.

JWT Claims Structure:
- Standard claims: sub (user_id), jti (token_id), iat, exp
- Custom claims: username, role, player_filter, provider_filter,
  token_name, is_long_lived

Token Types:
- Short-lived (30 days, auto-renewing on use, sliding window)
- Long-lived (10 years, no auto-renewal for API integrations)

Implementation:
- JWTHelper class for encoding/decoding with HS256 algorithm
- JWT secret key generated and stored in auth.db settings table
- Token verification tries JWT first, falls back to legacy hash lookup
- Database still stores tokens for revocation checking

Benefits:
- Stateless authentication (user info embedded in token)
- Permission scopes available without database lookup
- OAuth2/OIDC compatibility for external integrations
- Standard JWT format for third-party verification

Migration Strategy:
- Automatic: Old tokens work until expiration
- New logins get JWT tokens automatically
- No breaking changes for existing clients

* Fix JWT token expiration check to honor database expiration

Database expiration is the source of truth for token validity, not just
the JWT expiration claim. This ensures manual token expiration (via
database update) works correctly even when JWT exp is still valid.

* Add documentation for future OIDC support

Notes on consuming external OIDC vs acting as OIDC provider, and
refresh token requirements for the latter.

* Clean up JWT implementation: remove dead code and verbose comments

- Remove unused methods: refresh_short_lived_token(), get_user_from_token()
- Remove unused imports: timedelta, UserRole
- Move User import to TYPE_CHECKING block
- Remove TODO comment about refresh tokens (not implementing)
- Simplify inline comments and reduce verbosity
- All tests still passing (36/36)

* Remove player_filter and provider_filter from JWT claims

These values can be dynamically updated, so storing them in the token
would result in stale data. The current values are available from the
database lookup during token validation.

---------

Co-authored-by: Ztripez von Matérn <ztripez@bobby.se>
music_assistant/controllers/webserver/auth.py
music_assistant/helpers/jwt_auth.py [new file with mode: 0644]
pyproject.toml
requirements_all.txt