Skip to content

Authentication

Built-in accounts, OIDC, and forward auth can run side by side. Single sign-on is part of the free core.

MethodUse when
Built-in accountsYou want local usernames and passwords without an identity provider.
OIDCYou have an identity provider and want Lumastra to handle the login flow directly.
Forward authYour reverse proxy already authenticates users through an identity provider.

The iPhone, iPad and Apple TV apps work with all three: approve their device code in a signed-in browser.

Ways to sign in

Scroll, swipe, or click and drag to explore. Use the arrows to move, or fit the diagram above.

Ways to sign in iPhone, iPad, Apple TV show a code, get their owntoken You, in a browser web app, setup wizard, codeapproval Your identity provider Authentik · Authelia ·Keycloak Built-in accounts username and password Lumastra same account, whichever way approve the code forward auth or OIDC built-in login user and groups password

An empty database opens /setup. Its first screen, Check your install, shows what the server found before any account exists: the database, Valkey, the optional sidecars (sonic embedder, Live TV stream proxy), hardware transcoding and FFmpeg, whether the config volume keeps your settings, your reverse proxy and HTTPS, and the running nodes. Items marked with a warning are worth fixing first: a config folder that is not a mounted volume loses settings on restart, and a proxy missing from TRUSTED_PROXY_IPS breaks forward auth on the next screen. Nothing on that screen blocks setup; Recheck reads everything again after you fix something, and Copy as text gives you a summary to paste into a support request.

The wizard then creates the first admin. Single sign-on is recommended and is pre-selected: OIDC, or forward auth when the wizard sees an identity from a trusted reverse proxy. Built-in login is one click away if you have no identity provider.

  • OIDC: configure the provider first; the first sign-in must belong to an ADMIN_GROUPS group, otherwise no admin is created.
  • Forward auth: the forwarded user must belong to an ADMIN_GROUPS group.
  • Built-in: choose a username and password; this enables AUTH_NATIVE_LOGIN_ENABLED.

The next step is titled Your account is ready. Now the basics. Everything on it is optional and can be changed later in the admin settings, and it only shows what is wrong, such as a background worker that does not answer or a configured service that is down. Then you add your media folders and, if you like, connect Audiobookshelf. See Quick Start.

Setup stays locked after the first admin is created, even if the install later loses all admins. Restore the user’s admin group at the IdP (effective on their next forward-auth request or OIDC sign-in), or set SETUP_UNLOCK=1 in the container environment and restart. This reopens /setup only while no admin exists. Unset it once recovery is complete; settings.json cannot unlock setup. Local/dev recovery is also documented in scripts/setup-unlock.ts.

Lumastra stores accounts itself; users sign in at /login, and an admin creates and manages accounts in Admin → Users. No external service is involved.

  • Enabled by AUTH_NATIVE_LOGIN_ENABLED (set automatically when you create the first admin via the built-in path; you can also set it in env or admin settings).

  • Session lifetime is controlled by NATIVE_AUTH_SESSION_TTL_DAYS (default 30).

  • Sessions are database-backed, so they work across replicas. There’s no email-based password reset yet: an admin resets passwords from Admin → Users.

  • Locked out of the only admin account? Set a new password from the host. It prints a generated password and signs that user out everywhere:

    Terminal window
    docker compose exec lumastra npx tsx --tsconfig tsconfig.scripts.json scripts/reset-password.ts <username>

    To choose the password yourself, add -e LUMASTRA_NEW_PASSWORD='…' after exec.

Lumastra acts as an OpenID Connect relying party (Authorization Code + PKCE). When OIDC is enabled, the /login page (and the setup wizard) show a “Sign in with <provider>” button; the rest of the flow is your identity provider’s. An OIDC session is the same lumastra_session cookie a built-in login produces, so everything downstream behaves identically.

Enable it with three variables (it’s off by default):

AUTH_OIDC_ENABLED=true
AUTH_OIDC_ISSUER=https://idp.example.com/application/o/lumastra/
AUTH_OIDC_CLIENT_ID=your-client-id
AUTH_OIDC_CLIENT_SECRET=your-client-secret # omit for a public (PKCE-only) client
  • Redirect URI to register at your provider: https://your-domain/auth/oidc/callback (auto-derived from ORIGIN; override with AUTH_OIDC_REDIRECT_URI only if needed).
  • Admin is granted by the groups claim: AUTH_OIDC_GROUPS_CLAIM (default groups) matched case-insensitively against ADMIN_GROUPS, same as forward auth. Group sync is promote-only: it can grant admin but never silently removes it.
  • Account linking: a first OIDC login auto-links to an existing account only when the provider asserts a verified email matching just one account (AUTH_OIDC_LINK_BY_VERIFIED_EMAIL, on by default; never links on an unverified email). Linking by username is off by default (AUTH_OIDC_LINK_BY_USERNAME).

For a full, provider-by-provider walkthrough (Authentik, Keycloak, Google, and a local Dex test stack), see Single sign-on with OIDC.

Use this if you already run an identity provider and want SSO without per-app OIDC clients. Identity arrives in HTTP headers from your reverse proxy, and Lumastra trusts them only when forward auth is turned on and only from known peers.

Forward auth is off by default. Turn it on with:

Terminal window
FORWARD_AUTH_ENABLED=true

It can only be set as an environment variable (in .env or the container environment), like TRUSTED_PROXY_IPS. Admin → Settings shows its value but can’t change it, and a value in settings.json is ignored with a warning in the startup log.

While it’s off, Lumastra ignores the identity headers from every address, so setting TRUSTED_PROXY_IPS to get real client IP addresses can never sign anyone in. The setup wizard tells you when your proxy sends identity headers while forward auth is off.

Authentication paths

Scroll, swipe, or click and drag to explore. Use the arrows to move, or fit the diagram above.

Authentication paths Browser / user requests a page Reverse proxy Caddy · Traefik ·forward-auth Identity provider Authentik · authenticatesuser Lumastra auto-provisions user · admin= ADMIN_GROUPS Trust gate TRUSTED_PROXY_IPS ·fail-closed /api/v1 surface PAT · service · device JWT TV app / script bearer token · bypassesforward-auth request authenticate identity result X-Forwarded-User ·X-Forwarded-Groups trusted peer IP authenticated access Bearer token

Users are auto-provisioned on their first authenticated request. You don’t pre-create accounts; you manage users and their groups in your identity provider. Lumastra integrates with Jellyfin and matches usernames case-insensitively, so a forwarded username should match the corresponding Jellyfin username (case need not be identical).

Because identity arrives in plain HTTP headers, Lumastra only trusts them from known peers, and only with FORWARD_AUTH_ENABLED=true:

  • Set TRUSTED_PROXY_IPS to your reverse proxy’s address, or the address of every proxy in a chain. Required in production. List proxies only: with forward auth on, every address in a range such as 192.168.0.0/16 can send identity headers and sign in as anyone. The Security Self-Check warns about IPv4 ranges wider than /24. See Reverse proxy.
  • Or set LUMASTRA_TRUST_ANY_PROXY=true on a single private host where the container isn’t directly reachable.

The address Lumastra checks is the TCP peer it sees, which isn’t always your proxy’s own address. If the proxy runs on the Docker host and reaches the app through a published port, the peer is the Docker network’s gateway (for example 172.21.0.1), and trusting that trusts every process on the host. Run the proxy on the same compose network instead; see Deploy with Docker → Reverse proxy. If headers are rejected, the setup wizard and the logs (identity headers ignored — untrusted TCP peer) name the peer address that sent them.

A trusted proxy must also remove the identity headers a client sends. With forward auth on, Lumastra believes them from every address in TRUSTED_PROXY_IPS, so a proxy that passes a client’s X-Forwarded-User through lets anyone sign in as anyone. The setups in the Reverse proxy guide remove them. With forward auth off, Lumastra ignores and logs such headers, and npm run doctor and the admin health page warn about them.

The header names are configurable: USERHEADER (default X-Forwarded-User) and GROUPHEADER (default X-Forwarded-Groups). An optional third header, NAMEHEADER (default x-authentik-name), carries the user’s human-readable display name. If your proxy sends it, other users see it (for example, in “who else watched this”); leave it blank and Lumastra keeps showing the username. NAMEHEADER is trusted on the same terms as USERHEADER (forward auth on, TRUSTED_PROXY_IPS only).

nginx drops a header whose value is empty, so if your proxy always sends the groups header (Authentik’s outpost does), a user with no groups can be read as still having their previous groups (and admin rights). Either add the nginx map from the SSO guide (recommended), or turn on FORWARD_AUTH_GROUPS_ALWAYS_SENT (off by default) so a user with no groups is read as “no groups” instead. See Setting up Authentik for the full mechanism.

For a concrete walkthrough with Authentik (provider, reverse-proxy config, and the exact Lumastra env), see Single sign-on with Authentik.

For local development only, set DEFAULT_USER=<name>: in dev mode, requests with no auth header fall back to that user. A devUser cookie override lets you switch users without restarting. Both are gated on the build-time dev flag and never active in a production build.

All three can be on at once. For each request, Lumastra resolves identity in this order:

  1. A valid session cookie (built-in or OIDC) is honored first.
  2. Otherwise, forward-auth headers from a trusted proxy are used, if FORWARD_AUTH_ENABLED=true.
  3. Otherwise, browser visits are sent to /login (where the password form and/or the OIDC button appear, depending on what’s enabled), or, if only forward auth is configured, to your identity provider.

One safety rule: if a trusted proxy asserts a different username than the cookie, the stale cookie is cleared and the proxy wins (so a proxy-level logout takes effect even with a lingering session).

Identity headers are unsigned. Turn forward auth on only if you use it, restrict access to the app port, and trust only the proxy’s TCP peer, as described above. Otherwise a caller can forge an admin identity. MFA, password policy, lockout, and central logout are handled by your IdP; Lumastra stores no forward-auth password or session cookie.

Built-in login: the app is the credential custodian

Section titled “Built-in login: the app is the credential custodian”

Passwords use scrypt hashing. Login is rate-limited per IP, with per-account lockout and a server-side password policy. Sessions are database-backed, stored as hashes, and revoked on password change. Cookies are HttpOnly, SameSite=Lax, and Secure when the browser uses HTTPS (see the note above). Cross-site form posts are blocked by an origin check.

There is no built-in MFA or email password reset. Use strong passwords; admins reset user passwords at Admin → Users, or use the host command under Built-in login for admin recovery.

OIDC verifies PKCE, state, nonce, and the ID-token signature, issuer, audience, and expiry. Login transactions are single-use, and redirects stay on the same origin.

Keep verified-email account linking enabled and username linking disabled unless you understand the implications. Never set AUTH_OIDC_ALLOW_INSECURE in production: it disables TLS verification for OIDC traffic. The client secret is readable by admins in Settings. Set ORIGIN for secure session cookies.

Admin is granted by any of:

  • Forward auth: membership in a group named in ADMIN_GROUPS (default admin, matched case-insensitively); assign the group in your IdP.
  • OIDC: membership in an ADMIN_GROUPS group via the AUTH_OIDC_GROUPS_CLAIM claim (some providers need a scope/mapper to emit it; see the OIDC guide).
  • Built-in login: the first admin is created by the setup wizard; admins promote other accounts in Admin → Users (a manual promotion pins the user, overriding IdP groups).

Disable the account at Admin → Users to cut off sessions, device/refresh tokens, PATs, service tokens, and Jellyfin/Subsonic sessions at validation time. Credential revocation is additional best-effort cleanup; failures are reported in the disable result and logs. Re-applying disable is safe.

Removing IdP groups alone depends on correct empty-group header handling. Without it, a proxy that drops empty headers can leave stored groups and admin rights unchanged. See Removed-from-every-group users.

Apps and scripts use bearer tokens regardless of how browser users sign in. The two URL surfaces are kept separate intentionally: /api/* stays behind the forward-auth proxy (the IdP checks every browser request); /api/v1/* is bypassed by the proxy so token clients are never redirected to a login page.

RouteAuthUse
/api/*Browser session (built-in / OIDC) or forward authBrowser UI only
/api/v1/*Bearer token (JWT / PAT / service)TV apps, scripts, automation
/oauth/*PublicDevice authorization flow
/oauth/links/*Browser session (built-in / OIDC) or forward authAccount-linking callback (MyAnimeList)
/j/*Emby token / api_key param / jellyfin_compat PAT scopeJellyfin-compatible clients
/subsonic/*Subsonic u+t/s, p, or apiKey paramsSubsonic/OpenSubsonic clients

Three token kinds are accepted on /api/v1/*. The middleware routes validation by prefix:

KindPrefixIssued byFeature flagNotes
Device flow JWT(none: compact JWT)POST /oauth/tokenFEATURE_DEVICE_AUTHShort-lived (~1 h, HS256); carries device_session_id. Refresh token rotates on every use.
Personal Access Tokenpat_User: settings UI or POST /api/v1/me/patsFEATURE_USER_PATSScoped; max 10 per user; tracked (last-used IP + timestamp).
Service tokensvc_Admin: POST /api/v1/service-tokensFEATURE_DEVICE_AUTHPath/method-gated; optional act-as-user impersonation; IP allowlist.
Playback grant(opaque, sent as ?grant=)POST /api/v1/play-sessions(always on)Bound to one item and one playback session, expires (24 h for video and audiobooks, 6 h for music and Live TV), valid only on media URLs. Pinned to the requester’s IP unless the client asked for a cast target (castTarget) and STREAM_TOKEN_WILDCARD_IP allows it (default: allowed). STREAMING_SESSION_ENFORCE_IP=false turns IP pinning off entirely. Replaces stream_ tokens and api_key in media URLs.

All token values are shown once at creation and stored only as SHA-256 hashes. A signing secret is required for device JWTs and PATs. Set JWT_SECRET explicitly, or leave it unset and Lumastra derives one deterministically from LUMASTRA_MASTER_KEY (stable across restarts and replicas, so tokens keep working without extra configuration). If you do set it explicitly, keep the value identical everywhere: changing it invalidates every outstanding device and PAT token.

PATs and service tokens accept the grants read, write, and admin. Service tokens also accept integrations:read (see below); PATs also accept either exclusive Jellyfin-compatibility scope. Fine-grained scopes such as media:read or tokens:admin are internal gate names; they cannot be minted. Every operation in the API reference names the grant it needs (x-required-grant). The grants expand as follows:

  • read: media:read, sessions:read, and requests:read. Other read scopes are not granted by read.
  • write: read plus writable scopes; excludes tasks:run, webhooks:write, and admin scopes.
  • admin: all scopes.

See API & tokens for the full scope reference. The jellyfin_compat scopes remain exclusive to /j and are not included in these aliases.

Device authorization is enabled by default (FEATURE_DEVICE_AUTH). Clients need no registration or secret: the user approves a device code at /device, and the client receives a roughly one-hour access token plus a rotating refresh token. Replaying a consumed refresh token revokes the device session.

Set FEATURE_DEVICE_AUTH=false to disable it. Upgrades from the old disabled default turn it on unless you explicitly pin the flag. See feature flags.

For endpoint parameters, expiry settings, polling limits, and error handling, see OAuth device-flow clients.

Users create PATs in Settings → Account → API tokens (or via POST /api/v1/me/pats) once an admin turns on User PAT Creation under Admin → Settings → Access → Public API & Device Auth (FEATURE_USER_PATS, off by default). The toggle appears only while the Public API (FEATURE_DEVICE_AUTH) is on; with it off, set FEATURE_USER_PATS through the environment. Token creation needs a signing secret (explicit JWT_SECRET or one derived from the master key). FEATURE_DEVICE_AUTH matters only for where a minted PAT can authenticate: without it, a PAT still works against the /j Jellyfin-compatible surface, but is rejected on /api/v1/*. Turn both flags on to use PATs against /api/v1/*. Each PAT carries explicit scopes; only admins may request admin scope. Maximum 10 active PATs per user.

Manage PATs:

MethodEndpointAuth required
GET/api/v1/me/patsUser session or bearer
POST/api/v1/me/patsUser session or bearer
DELETE/api/v1/me/pats/{id}User session or bearer

Admins create service tokens (POST /api/v1/service-tokens) for automation and IaC. Service tokens support:

  • A grant list (scopes): read, write, admin, or integrations:read.
  • Optional IP allowlist (allowedIps): exact IPv4 or IPv6 addresses, no CIDR ranges.
  • Optional path/method gate (allowedPathPrefixes, allowedMethods).
  • Optional act-as-user impersonation (actAsUserId, a user id set at creation; the X-Act-As-User header is ignored).
  • An owner: the admin who created it. The token follows that account by id, so renaming the owner keeps it working, and disabling or deleting the owner stops it.
  • Minting by another service token: an admin service token can mint tokens. They belong to that token’s owner and can never exceed the minting token. See Tokens minted by a service token.

Integration tokens. Dashboards and other read-only integrations use a service token with the single grant integrations:read. It works only on GET /api/v1/integrations/homepage, /watch-history and /upcoming, only while the admin who created it is still an admin, and it cannot act as a user. Admin sessions and admin tokens cannot read watch history or upcoming episodes. Upgrading converts existing admin tokens that were limited to those endpoints (GET only) into integration tokens.

Manage service tokens:

MethodEndpointAuth required
GET/api/v1/service-tokensadmin grant
POST/api/v1/service-tokensadmin grant
DELETE/api/v1/service-tokens/{id}admin grant

For the full walkthrough (device flow code samples, playback grants, and library automation), see API & tokens.