-
Notifications
You must be signed in to change notification settings - Fork 2
Authentication
🌐 English · Deutsch
Authentication is enabled by default. On first start the admin account is created via a setup token (Getting Started).
| Role | Permissions |
|---|---|
| admin | Everything — incl. settings, encoding, user management, library management (delete/rename/move), custom paths, AutoSync management |
| user | Search, downloads, own favourites, own notifications, view/play the library |
| kids | Like user, but age-limited and read-only: no downloads, no Auto-Sync, no module store, no module settings |
The kids role is a restriction, not a rank below user. An account that has it sees an age-limited app and cannot change that — there is no PIN, because there is nothing to leave. The limit is the one set under Settings → Start Page → Kids mode → Age limit (default 6) and is applied by the server at every point, not by the browser:
| Where | What happens |
|---|---|
| Home feed | Titles rated above the limit are dropped while the rows are built; the 18+ source is never fetched |
| Search | The adult source answers 403; results are filtered against the cached TMDB rating |
| Advanced search | TMDB is queried with certification.lte, so the paging is already limited |
| Library | The shelf is filtered against the cached TMDB rating |
| Playback |
/api/stream/start refuses the file itself — a copied or guessed path does not play either |
| Downloads |
/api/download and Auto-Sync answer 403
|
| Module store, module settings | Redirect / 403
|
Be clear about what this can and cannot do. Filtering can only judge a title TMDB has a certification for, and unrated titles are shown on purpose — dropping everything unrated would empty the app on an instance without a TMDB key, and an empty app is one people switch the protection off for. The parts that do not depend on metadata at all — the refusals in the table above — are what actually holds.
Don't confuse it with the kids mode on the home page: that is for a shared account with a child in front of it right now, it is entered by anyone and left with a PIN. The role is for a child with their own login.
User management (create, delete, change role) is done by admins on the settings page. Rules: username max. 64 characters (a–z, 0–9, ., _, -), password min. 8 characters. You cannot delete your own account. Role changes apply without re-login (the session role is refreshed from the DB every 15 s).

- Sessions are valid for 24 h (HTTPOnly, SameSite=Lax; Secure flag with HTTPS).
-
Login rate limit: max. 10 login attempts per minute (optional Redis backend via
MEDIAFORGE_REDIS_URL). -
CSRF protection for forms; JSON API routes require
Content-Type: application/json. - Security headers (CSP, X-Frame-Options DENY, nosniff, Referrer-Policy; HSTS with HTTPS).
- Session fixation protection: the session is recreated on every login.
- The Flask secret key lives in
~/.mediaforge/.flask_secret(file mode 0600).
HTTPS: Behind a TLS reverse proxy, set
web_base_urlto thehttps://URL or exportMEDIAFORGE_HTTPS=1— otherwise session cookies are not marked "Secure" (the app logs a warning).
Single sign-on with any OpenID Connect provider (Keycloak, Authentik, Authelia, …). Configure under Settings → SSO or via environment variables:
| Setting | DB key | Env variable |
|---|---|---|
| Enable SSO | web_sso |
MEDIAFORGE_WEB_SSO |
| SSO only (disable local login) | web_force_sso |
MEDIAFORGE_WEB_FORCE_SSO |
| Issuer URL | oidc_issuer_url |
MEDIAFORGE_OIDC_ISSUER_URL |
| Client ID | oidc_client_id |
MEDIAFORGE_OIDC_CLIENT_ID |
| Client secret | oidc_client_secret |
MEDIAFORGE_OIDC_CLIENT_SECRET |
| Button display name | oidc_display_name |
MEDIAFORGE_OIDC_DISPLAY_NAME |
| Admin username | oidc_admin_user |
MEDIAFORGE_OIDC_ADMIN_USER |
Admin subject (sub claim) |
oidc_admin_subject |
MEDIAFORGE_OIDC_ADMIN_SUBJECT |
Notes:
- The discovery URL is derived automatically from the issuer URL (
<issuer>/.well-known/openid-configuration); scopes:openid email profile. - Register
<base URL>/oidc/callbackas the redirect URI at your provider. - SSO users are created automatically on first login. If the username or
subclaim matchesoidc_admin_user/oidc_admin_subject, the user is granted admin rights. - Changes to the SSO configuration require a restart.
- With Force SSO the local login form is disabled entirely.
The /api/v1/... endpoints don't use session login but an API key in the X-Api-Key header. The key is generated automatically on first start and can be viewed and regenerated in the settings (API Reference).
Internally a no-auth mode exists (virtual admin), but the standard launch always enables authentication. Never expose an instance without auth to untrusted networks.
Three fixed roles have no middle: to let somebody approve downloads you had to make them an admin, which also handed them the settings, the module store and every other account.
A group bundles two things that always travelled together anyway: a set of permissions (library.read, queue.write, settings.admin, …) and a library scope — which library locations its members may see at all. Keeping them in one object is deliberate; splitting them means every "why can this person not see that folder?" question has two places to look, and they drift.
Groups are managed in Settings → Authentication → Groups & Permissions, next to the user list.
admin, user and kids exist as built-in groups matching the three roles. They cannot be renamed, edited or deleted: they define what the roles mean app-wide, the decorators rely on them, and editing them from the UI would let an admin lock themselves out of the settings page that fixes it.
kids is a restriction, not a rank. Note the absence of adult.view — the age gate is expressed as a permission rather than as another role-name comparison scattered through the routes.
A user's effective permissions are the union of the built-in group matching their role and every custom group they belong to. That makes groups purely additive: nobody loses access on upgrade, and admin_required keeps working unchanged because admin still holds the wildcard.
Library scope works the other way round: an explicit scope beats the wildcard. Every user is in a built-in group whose scope is *, so if the wildcard simply won, scoping would be permanently dead. As soon as any of a user's groups names concrete locations, the user is restricted to the union of those. Admins are never scoped — an admin who cannot see a library cannot fix it either.
A permission this build does not know is dropped when you save, not stored: a permission nothing checks looks granted in the UI and does nothing, and that is how people end up believing an account is restricted when it is not.
Every group change is recorded in the Audit Log.
A group's scope names the library locations its members may see: default for the download root, or a custom path's id. The group editor lists them by name, so nobody has to remember an id — a scope naming a location that does not exist looks configured and restricts nothing.
Enforcement is at the choke points rather than per route, which is the only way it stays true as routes are added:
-
/api/libraryand/api/library/overviewdrop out-of-scope locations, including from the counters. Counting everything and showing some of it is how a "restricted" view leaks exactly what it was meant to hide. -
lib_resolve_library_file()— the single "may the caller touch this file?" answer, used by delete, rename, media-info, the eBook and comic routes and the upscale queue — refuses paths outside the caller's scope. Background workers passscoped=False: they have no session, and an upscale job must not fail because the account that queued it was later restricted. - The player's
_resolve_media_path()applies it too. "Cannot see it in the library but can still stream it by path" is not a restriction.
Scanning deliberately stays unscoped. The index has to cover the whole library regardless of who happens to be logged in when a scan runs; filtering there would make the cache depend on the session that populated it.
🇬🇧 English
Users
- Installation
- Getting Started
- Migration from AniWorld
- Configuration
- Web UI
- Download System
- Download History
- AutoSync
- Calendar
- Library
- Authentication
- Notifications
- Integrations
- SyncPlay
- Anime4K Upscaling
- Encoding
- Modules
- Theme Packs
- Backup
- Operations
- Audit Log
- Rules & Languages
- Docker
- Supported Sites
Developers
🇩🇪 Deutsch
Benutzer
- Installation
- Erste Schritte
- Umzug von AniWorld
- Konfiguration
- Web-UI
- Download-System
- Download-Verlauf
- AutoSync
- Kalender
- Bibliothek
- Authentifizierung
- Benachrichtigungen
- Integrationen
- SyncPlay
- Anime4K-Upscaling
- Encoding
- Module
- Theme-Pakete
- Backup
- Betrieb
- Audit-Log
- Regeln & Sprachen
- Docker
- Unterstützte Seiten
Entwickler