Pascal Linxweiler fda59741b5
All checks were successful
Build Plugin / build (push) Successful in 1m1s
feat: sync the profile picture from the OIDC provider
Pocket ID exposes an avatar through the standard picture claim, pointing at
/api/users/{id}/profile-picture.png. On login the plugin now fetches that URL
and writes it to the Jellyfin user's profile image, so SSO users get their
avatar without setting one by hand.

The picture is fetched anonymously. The access token is deliberately not sent,
because the address ultimately originates from a token payload and must not be
treated as a trusted destination for credentials. Non-http(s) URLs, non-image
content types and anything over 5 MB are rejected, and an identical image is
not rewritten so the image cache is left alone. Any failure is logged and
swallowed: a broken avatar must never block a login.

Controlled by the new "Sync the profile picture" setting, on by default, with
the claim name configurable for providers that do not use "picture".

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-29 15:41:51 +02:00
2026-07-29 13:26:22 +00:00
2026-07-29 13:26:22 +00:00
2026-07-29 13:26:22 +00:00

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://<jellyfin>/OidcAuth/login.
  2. Plugin redirects to your provider (authorization code flow + PKCE via IdentityModel.OidcClient).
  3. Provider redirects back to https://<jellyfin>/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_<version>.zip:

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:

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.

  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_<version>.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://<jellyfin>/OidcAuth/callback

Then in Jellyfin: Dashboard → Plugins → OIDC Auth:

Setting Meaning
Issuer URL Provider base URL; discovery doc must exist at <issuer>/.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. Add a button via Dashboard → General → Branding → Login disclaimer:

<a is="emby-linkbutton" class="raised block emby-button" href="/OidcAuth/login">Sign in with SSO</a>

TV and native clients

The plugin does not change or disable Jellyfin's normal password login, so TV apps (Android TV, Roku, Swiftfin, Kodi, ...) keep working exactly as before. For users that exist only through OIDC, three options:

  1. Quick Connect (recommended): enable it in Dashboard → General. On the TV choose Quick Connect, then approve the code from the web client (where you're logged in via OIDC).
  2. Set a password: after the first OIDC login, the user sets a password in their web profile and uses that on the TV.
  3. Blank password (LAN-only setups): turn off "Set random password for auto-created users" — TV login then works with username + empty password. Do not do this on a server reachable from the internet.

Notes / limitations

  • The OIDC browser flow itself works for the web client (and anything that can open a browser and reuse the resulting session token). Native clients that only show the username/password form can't use this flow directly — see the TV section above.
  • Behind a reverse proxy, make sure Jellyfin sees the external scheme/host (X-Forwarded-Proto, X-Forwarded-Host — Jellyfin's "Known proxies" setting), otherwise the computed redirect URI won't match the one registered at the provider.
  • Password login stays enabled; this plugin adds an additional login path.
Description
No description provided
Readme 567 KiB
v1.0.4.0 Latest
2026-07-29 13:25:12 +00:00
Languages
C# 57.1%
Python 23.3%
HTML 19.6%