# Jellyfin OIDC Auth Plugin Log in to Jellyfin with any OpenID Connect provider (Authelia, Authentik, Keycloak, Pocket ID, Google, ...). ## How it works 1. User opens `https:///OidcAuth/login`. 2. Plugin redirects to your provider (authorization code flow + PKCE via `IdentityModel.OidcClient`). 3. Provider redirects back to `https:///OidcAuth/callback`; the plugin exchanges the code, validates the tokens, and maps claims to a Jellyfin user (creating it if configured). 4. A small callback page creates a Jellyfin session (`ISessionManager.AuthenticateDirect`), writes the credentials into the web client's local storage, and redirects to `/web/`. ## Build Requires the .NET 9 SDK and `pyyaml`. The script compiles the plugin and packages it the way Jellyfin expects — the DLLs listed in `build.yaml` plus a generated `meta.json` — into `dist/oidc-auth_.zip`: ```bash pip install pyyaml python scripts/build_plugin.py ``` Pass `--version 1.0.4.0` to override the version in `build.yaml`. ## Release Releases are cut by pushing an annotated tag; the tag message becomes the changelog: ```bash git tag -a v1.0.4.0 -m "What changed in this release." git push origin v1.0.4.0 ``` `.gitea/workflows/release.yml` then builds the plugin at that tag, commits the zip to `dist/` on `main` together with an updated `manifest.json`, and attaches the zip to the Gitea release. Jellyfin picks the new version up from the repository URL below. Pushing needs a token with write access. Gitea's built-in `GITHUB_TOKEN` is used by default; if the repository does not grant it write access, add a PAT as the `RELEASE_TOKEN` secret. ## Install via plugin repository (recommended) 1. In Jellyfin: Dashboard → Plugins → Repositories → **+** 2. Repository URL: ``` https://git.linxweiler.xyz/pascallinxweiler/jellyfinsso/raw/branch/main/manifest.json ``` 3. Catalog → Authentication → **OIDC Auth** → Install → restart Jellyfin. Updates show up in the catalog automatically when a new version is added to `manifest.json`. ## Install manually Extract `dist/oidc-auth_.zip` into a new folder in Jellyfin's plugin directory (e.g. `config/plugins/OidcAuth/`), then restart Jellyfin. The zip holds: - `Jellyfin.Plugin.OidcAuth.dll` - `IdentityModel.OidcClient.dll` - `IdentityModel.dll` - `meta.json` Requires Jellyfin 10.11.x. ## Configure At your OIDC provider, create a **confidential web client** with redirect URI: ``` https:///OidcAuth/callback ``` Then in Jellyfin: Dashboard → Plugins → OIDC Auth: | Setting | Meaning | |---|---| | Issuer URL | Provider base URL; discovery doc must exist at `/.well-known/openid-configuration` | | Client ID / Secret | From your provider | | Scopes | Default `openid profile email`; add `groups` if roles come via that scope | | Username claim | Default `preferred_username`, falls back to `sub` | | Role claim | Default `groups` | | Allowed roles | CSV; empty = every authenticated user may log in | | Admin roles | CSV; users with one of these roles are granted admin on SSO login. Missing role does **not** revoke admin unless "Also revoke admin" is enabled. Empty = plugin never touches the admin flag | | Create users on first login | On by default; new users get access to all libraries | | Set random password for auto-created users | On by default. Without it, Jellyfin accepts a **blank password** for these users from any client — convenient on a LAN-only server, dangerous on an exposed one | | Disable strict endpoint validation | Enable for Google and other providers whose endpoints are on a different host than the issuer | ## Login button on the web login page Jellyfin does not let plugins modify the login page. The login disclaimer is the only injection point, and it needs a little CSS to look like a real button. Dashboard → General → Branding → **Login disclaimer**: ```html Sign in with SSO ``` `cancel` gives the grey look of the Quick Connect button; swap it for `button-submit` to match the blue Sign In button. Do not copy `btnQuick` off the Quick Connect button — that class is what the login page binds Quick Connect to, not styling. Dashboard → General → Branding → **Custom CSS**: ```css .disclaimerContainer { display: block; } a.raised.emby-button { padding: 0.9em 1em; color: inherit !important; text-decoration: none; } ``` Both pieces are needed. Jellyfin renders the disclaimer through markdown-it and then DOMPurify, which strips the `is="emby-linkbutton"` attribute that would normally turn the anchor into a button component — so the styling has to come from the classes instead. `.disclaimerContainer` is not a block by default, which is what otherwise leaves the link shrink-wrapped and centred instead of matching the width of the buttons above it. It has to be an anchor. Copying the markup of a real login button does not work: `