From 07fb48626dfdc70e6021a2bc27d9215ab88f832d Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sat, 21 Feb 2026 20:05:44 -0800 Subject: [PATCH] Add Authentik SSO integration for Jellyfin (#239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add Authentik OIDC provider + application for Jellyfin via blueprint (all authenticated users allowed, no policy binding) - Wire `jellyfin-client-secret` through ExternalSecret and Authentik worker deployment - Install [jellyfin-plugin-sso](https://github.com/9p4/jellyfin-plugin-sso) v4.0.0.3 via Ansible, with OIDC config template - Authentik `admins` group maps to Jellyfin administrator role - Local login left enabled; SSO is additive ## Deployment and Testing - [ ] Sync ArgoCD `authentik` app on branch — verify provider + application appear in Authentik admin - [ ] `mise run provision-indri -- --tags jellyfin --check --diff` (dry run) - [ ] `mise run provision-indri -- --tags jellyfin` (deploy plugin + config) - [ ] Test SSO flow: `https://jellyfin.ops.eblu.me/sso/OID/start/authentik` - [ ] Verify `eblume` account auto-links via `preferred_username` match - [ ] Verify admins group → Jellyfin admin - [ ] Reset ArgoCD app revision to main after merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/239 --- ansible/playbooks/indri.yml | 17 ++++++++ ansible/roles/jellyfin/defaults/main.yml | 7 ++++ ansible/roles/jellyfin/tasks/main.yml | 33 +++++++++++++++ .../roles/jellyfin/templates/sso-auth.xml.j2 | 33 +++++++++++++++ .../authentik/configmap-blueprint.yaml | 42 +++++++++++++++++++ .../authentik/deployment-worker.yaml | 5 +++ .../manifests/authentik/external-secret.yaml | 4 ++ .../feature-jellyfin-authentik-sso.feature.md | 1 + 8 files changed, 142 insertions(+) create mode 100644 ansible/roles/jellyfin/templates/sso-auth.xml.j2 create mode 100644 docs/changelog.d/feature-jellyfin-authentik-sso.feature.md diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index c2bb67a..51d2db7 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -161,6 +161,23 @@ no_log: true tags: [caddy] + # Jellyfin SSO client secret + - name: Fetch Jellyfin OIDC client secret + ansible.builtin.command: + cmd: op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/oor7os5kapczgpbwv7obkca4y4/jellyfin-client-secret" + delegate_to: localhost + register: _jellyfin_oidc_secret + changed_when: false + no_log: true + check_mode: false + tags: [jellyfin] + + - name: Set Jellyfin OIDC client secret fact + ansible.builtin.set_fact: + jellyfin_sso_client_secret: "{{ _jellyfin_oidc_secret.stdout }}" + no_log: true + tags: [jellyfin] + # Jellyfin API key for metrics collection - name: Fetch Jellyfin API key ansible.builtin.command: diff --git a/ansible/roles/jellyfin/defaults/main.yml b/ansible/roles/jellyfin/defaults/main.yml index 6c96d34..380e625 100644 --- a/ansible/roles/jellyfin/defaults/main.yml +++ b/ansible/roles/jellyfin/defaults/main.yml @@ -21,3 +21,10 @@ jellyfin_webdir: "{{ jellyfin_cask_app_path }}/Contents/Resources/jellyfin-web" # Log directory jellyfin_log_dir: "{{ ansible_env.HOME }}/Library/Logs" + +# SSO plugin configuration +jellyfin_sso_plugin_version: "4.0.0.3" +jellyfin_sso_client_id: jellyfin +jellyfin_sso_client_secret: "" +jellyfin_sso_provider_name: authentik +jellyfin_plugins_dir: "{{ jellyfin_data_dir }}/plugins" diff --git a/ansible/roles/jellyfin/tasks/main.yml b/ansible/roles/jellyfin/tasks/main.yml index 6a92aa4..bf213ee 100644 --- a/ansible/roles/jellyfin/tasks/main.yml +++ b/ansible/roles/jellyfin/tasks/main.yml @@ -28,3 +28,36 @@ when: jellyfin_launchctl_check.rc != 0 changed_when: true failed_when: false + +# SSO plugin installation +- name: Ensure SSO-Auth plugin directory exists + ansible.builtin.file: + path: "{{ jellyfin_plugins_dir }}/SSO-Auth_{{ jellyfin_sso_plugin_version }}" + state: directory + mode: '0755' + +- name: Download SSO-Auth plugin archive + ansible.builtin.get_url: + url: "https://github.com/9p4/jellyfin-plugin-sso/releases/download/v{{ jellyfin_sso_plugin_version }}/sso-authentication_{{ jellyfin_sso_plugin_version }}.zip" + dest: "/tmp/sso-authentication_{{ jellyfin_sso_plugin_version }}.zip" + mode: '0644' + +- name: Extract SSO-Auth plugin + ansible.builtin.unarchive: + src: "/tmp/sso-authentication_{{ jellyfin_sso_plugin_version }}.zip" + dest: "{{ jellyfin_plugins_dir }}/SSO-Auth_{{ jellyfin_sso_plugin_version }}" + remote_src: true + notify: Reload jellyfin + +- name: Ensure plugin configurations directory exists + ansible.builtin.file: + path: "{{ jellyfin_plugins_dir }}/configurations" + state: directory + mode: '0755' + +- name: Deploy SSO-Auth plugin configuration + ansible.builtin.template: + src: sso-auth.xml.j2 + dest: "{{ jellyfin_plugins_dir }}/configurations/SSO-Auth.xml" + mode: '0644' + notify: Reload jellyfin diff --git a/ansible/roles/jellyfin/templates/sso-auth.xml.j2 b/ansible/roles/jellyfin/templates/sso-auth.xml.j2 new file mode 100644 index 0000000..8e8bd76 --- /dev/null +++ b/ansible/roles/jellyfin/templates/sso-auth.xml.j2 @@ -0,0 +1,33 @@ + + + + + + + {{ jellyfin_sso_provider_name }} + + + https://authentik.ops.eblu.me/application/o/jellyfin + {{ jellyfin_sso_client_id }} + {{ jellyfin_sso_client_secret }} + true + true + true + + admins + + false + + groups + + openid + email + profile + + https + + + + + + diff --git a/argocd/manifests/authentik/configmap-blueprint.yaml b/argocd/manifests/authentik/configmap-blueprint.yaml index 5501811..f5b4784 100644 --- a/argocd/manifests/authentik/configmap-blueprint.yaml +++ b/argocd/manifests/authentik/configmap-blueprint.yaml @@ -245,3 +245,45 @@ data: enabled: true negate: false timeout: 30 + + jellyfin.yaml: | + version: 1 + metadata: + name: BlumeOps Jellyfin SSO + labels: + blueprints.goauthentik.io/description: "Jellyfin OIDC provider and application" + entries: + # OAuth2 provider for Jellyfin + - model: authentik_providers_oauth2.oauth2provider + id: jellyfin-provider + identifiers: + name: Jellyfin + attrs: + name: Jellyfin + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + client_type: confidential + client_id: jellyfin + client_secret: !Env AUTHENTIK_JELLYFIN_CLIENT_SECRET + redirect_uris: + - matching_mode: strict + url: https://jellyfin.ops.eblu.me/sso/OID/redirect/authentik + signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]] + property_mappings: + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]] + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]] + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]] + sub_mode: hashed_user_id + include_claims_in_id_token: true + + # Jellyfin application — all authenticated users allowed (no policy binding) + - model: authentik_core.application + id: jellyfin-app + identifiers: + slug: jellyfin + attrs: + name: Jellyfin + slug: jellyfin + provider: !KeyOf jellyfin-provider + meta_launch_url: https://jellyfin.ops.eblu.me + policy_engine_mode: all diff --git a/argocd/manifests/authentik/deployment-worker.yaml b/argocd/manifests/authentik/deployment-worker.yaml index bbc421f..9bcc9fd 100644 --- a/argocd/manifests/authentik/deployment-worker.yaml +++ b/argocd/manifests/authentik/deployment-worker.yaml @@ -68,6 +68,11 @@ spec: secretKeyRef: name: authentik-config key: zot-client-secret + - name: AUTHENTIK_JELLYFIN_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: authentik-config + key: jellyfin-client-secret volumeMounts: - name: blueprints mountPath: /blueprints/custom diff --git a/argocd/manifests/authentik/external-secret.yaml b/argocd/manifests/authentik/external-secret.yaml index 0f4b639..f0218b9 100644 --- a/argocd/manifests/authentik/external-secret.yaml +++ b/argocd/manifests/authentik/external-secret.yaml @@ -49,3 +49,7 @@ spec: remoteRef: key: "Authentik (blumeops)" property: zot-client-secret + - secretKey: jellyfin-client-secret + remoteRef: + key: "Authentik (blumeops)" + property: jellyfin-client-secret diff --git a/docs/changelog.d/feature-jellyfin-authentik-sso.feature.md b/docs/changelog.d/feature-jellyfin-authentik-sso.feature.md new file mode 100644 index 0000000..67de207 --- /dev/null +++ b/docs/changelog.d/feature-jellyfin-authentik-sso.feature.md @@ -0,0 +1 @@ +Add Authentik SSO to Jellyfin with admin group mapping