diff --git a/ansible/group_vars/all/oas.yml b/ansible/group_vars/all/oas.yml
index 77ee8c694a7b2bb622d099c784e7f25f9f99f014..6f4284c444914583f08a3dde1975d49c0d124548 100644
--- a/ansible/group_vars/all/oas.yml
+++ b/ansible/group_vars/all/oas.yml
@@ -21,6 +21,14 @@ onlyoffice_postgresql_password: "{{ lookup('password', '{{ cluster_dir }}/secret
 onlyoffice_rabbitmq_password: "{{ lookup('password', '{{ cluster_dir }}/secrets/onlyoffice_rabbitmq_password chars=ascii_letters') }}"
 grafana_admin_password: "{{ lookup('password', '{{ cluster_dir }}/secrets/grafana_admin_password chars=ascii_letters') }}"
 
+# Single sign-on passwords
+userpanel_oauth_client_secret: "{{ lookup('password', '{{ cluster_dir }}/secrets/userpanel_oauth_client_secret chars=ascii_letters') }}"
+userbackend_postgres_password: "{{ lookup('password', '{{ cluster_dir }}/secrets/userbackend_postgres_password chars=ascii_letters') }}"
+userbackend_admin_username: "admin"
+userbackend_admin_password: "{{ lookup('password', '{{ cluster_dir }}/secrets/userbackend_admin_username chars=ascii_letters') }}"
+userbackend_admin_email: "email@example.net"
+hydra_system_secret: "{{ lookup('password', '{{ cluster_dir }}/secrets/hydra_system_secret chars=ascii_letters') }}"
+
 # Application versions
 helm:
   # helm snap 2.15 broke for us
diff --git a/ansible/roles/apps/tasks/main.yml b/ansible/roles/apps/tasks/main.yml
index 47f0e897ff29c498725569fea5d061d8fb80d5b1..b345c18955ec22669dc1bb6f1213b9cbeed641bd 100644
--- a/ansible/roles/apps/tasks/main.yml
+++ b/ansible/roles/apps/tasks/main.yml
@@ -28,3 +28,6 @@
 
 - name: Tasks pertaining to NextCloud
   import_tasks: nextcloud.yml
+
+- name: Tasks pertaining to Single sign-on
+  import_tasks: single-sign-on.yml
diff --git a/ansible/roles/apps/tasks/single-sign-on.yml b/ansible/roles/apps/tasks/single-sign-on.yml
new file mode 100644
index 0000000000000000000000000000000000000000..92a54d65e1683349a3a352db59ecb6808b931f2e
--- /dev/null
+++ b/ansible/roles/apps/tasks/single-sign-on.yml
@@ -0,0 +1,11 @@
+---
+
+- name: Create Kubernetes secret with single-sign-on settings
+  tags:
+    - config
+    - flux
+    - single-sign-on
+  vars:
+    flux:
+      name: "single-sign-on-settings"
+  include_tasks: flux_secret.yml
diff --git a/ansible/roles/apps/templates/single-sign-on-settings.yaml b/ansible/roles/apps/templates/single-sign-on-settings.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..44d66090a2efa21daf262cc82d35942008c48e75
--- /dev/null
+++ b/ansible/roles/apps/templates/single-sign-on-settings.yaml
@@ -0,0 +1,62 @@
+replicaCount: 1
+
+consentProviderImage:
+  << : &IMAGE_DEFAULTS_SSO { tag: "master", pullPolicy: "Always" }
+  repository: "open.greenhost.net:4567/openappstack/single-sign-on/consent_provider"
+loginProviderImage:
+  << : *IMAGE_DEFAULTS_SSO
+  repository: "open.greenhost.net:4567/openappstack/single-sign-on/login_provider"
+
+singleSignOnHost: &SSO_HOST "sso.{{ domain }}"
+
+userpanel:
+  image:
+    << : &IMAGE_DEFAULTS_USER_PANEL { tag: "master", pullPolicy: "Always" }
+    repository: "open.greenhost.net:4567/openappstack/user-panel/frontend"
+  ingress:
+    host: "admin.{{ domain }}"
+  oAuthClientSecret: "{{ userpanel_oauth_client_secret }}"
+
+userbackend:
+  image:
+    << : *IMAGE_DEFAULTS_USER_PANEL
+    repository: "open.greenhost.net:4567/openappstack/user-panel/backend"
+  username: "{{ userbackend_admin_username }}"
+  password: "{{ userbackend_admin_password }}"
+  email: "{{ userbackend_admin_email }}"
+  postgresImage: postgres
+  postgresTag: 11
+  postgresPullPolicy: Always
+  postgresPassword: "{{ userbackend_postgres_password }}"
+  persistence:
+    enabled: false
+    annotations:
+      size: 1Gi
+      storageClass: "-"
+
+hydra:
+  hydra:
+    dangerousForceHttp: true
+    config:
+      dsn: memory
+      urls:
+        self:
+          issuer: "https://sso.{{ domain }}"
+        login: "https://sso.{{ domain }}/login"
+        consent: "https://sso.{{ domain }}/consent"
+      secrets:
+        system: "{{ hydra_system_secret }}"
+  ingress:
+    public:
+      enabled: true
+      annotations:
+        kubernetes.io/tls-acme: "true"
+      hosts:
+        - host: *SSO_HOST
+          paths: ["/"]
+      tls:
+        - hosts:
+          - *SSO_HOST
+          secretName: hydra-public.tls
+    admin:
+      enabled: false
diff --git a/flux/single-sign-on.yaml b/flux/single-sign-on.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..dffe3821ef7d9ae070f4595418e221eb2e463faa
--- /dev/null
+++ b/flux/single-sign-on.yaml
@@ -0,0 +1,19 @@
+---
+apiVersion: helm.fluxcd.io/v1
+kind: HelmRelease
+metadata:
+  name: single-sign-on
+  namespace: oas
+  annotations:
+    flux.weave.works/automated: "false"
+spec:
+  releaseName: single-sign-on
+  chart:
+    git: https://open.greenhost.net/openappstack/single-sign-on
+    ref: 0d57810b0380baecda2d7f784898203a258bc366
+    path: ./helmchart/single-sign-on/
+  valuesFrom:
+    - secretKeyRef:
+        name: single-sign-on-settings
+        key: values.yaml
+  timeout: 1800