diff --git a/flux2/apps/monitoring/eventrouter-values-configmap.yaml b/flux2/apps/monitoring/eventrouter-values-configmap.yaml
index 65b6d671012d99cb416362210edae62aad3d4d94..fa7d5eb712ca6608cf8e7684b88f6db44038c487 100644
--- a/flux2/apps/monitoring/eventrouter-values-configmap.yaml
+++ b/flux2/apps/monitoring/eventrouter-values-configmap.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   name: stackspin-eventrouter-values
+  namespace: stackspin
 data:
   values.yaml: |
     sink: stdout
diff --git a/flux2/apps/monitoring/kube-prometheus-stack-oauth-client.yaml b/flux2/apps/monitoring/kube-prometheus-stack-oauth-client.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..82106d0d4bc42cf12f08de55090f643aa734f12b
--- /dev/null
+++ b/flux2/apps/monitoring/kube-prometheus-stack-oauth-client.yaml
@@ -0,0 +1,22 @@
+apiVersion: hydra.ory.sh/v1alpha1
+kind: OAuth2Client
+metadata:
+  name: kube-prometheus-stack-oauth-client
+  # Has to live in the same namespace as the stackspin-wordpress-oauth-variables
+  # secret
+  namespace: flux-system
+spec:
+  grantTypes:
+    - authorization_code
+    - refresh_token
+    - client_credentials
+  responseTypes:
+    - id_token
+    - code
+  scope: "openid profile email stackspin_roles"
+  secretName: stackspin-kube-prometheus-stack-oauth-variables
+  # these are optional
+  redirectUris:
+    - https://grafana.${domain}/login/generic_oauth
+  # hydraAdmin: {}
+  tokenEndpointAuthMethod: client_secret_post
diff --git a/flux2/apps/monitoring/kube-prometheus-stack-values-configmap.yaml b/flux2/apps/monitoring/kube-prometheus-stack-values-configmap.yaml
index 479ad58d58695610b6b65b69c874d7e339fb51e1..5d65d1c59497a27102d38bcc7434b7b7100cd2d9 100644
--- a/flux2/apps/monitoring/kube-prometheus-stack-values-configmap.yaml
+++ b/flux2/apps/monitoring/kube-prometheus-stack-values-configmap.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   name: stackspin-kube-prometheus-stack-values
+  namespace: stackspin
 data:
   values.yaml: |
     # https://github.com/prometheus-community/helm-charts/blob/main/charts/kube-prometheus-stack/values.yaml
@@ -153,8 +154,8 @@ data:
         auth.generic_oauth:
           name: Stackspin
           enabled: true
-          client_id: grafana
-          client_secret: "${grafana_oauth_client_secret}"
+          client_id: kube-prometheus-stack
+          client_secret: "${client_secret}"
           scopes: "openid profile email stackspin_roles"
           auth_url: "https://sso.${domain}/oauth2/auth"
           token_url: "https://sso.${domain}/oauth2/token"
diff --git a/flux2/apps/monitoring/kustomization.yaml b/flux2/apps/monitoring/kustomization.yaml
index 1b30e22f87d3dd1eec895cc42580c1f9e9d87519..d3c8daccb628557825f849a4f4852a5407673dfb 100644
--- a/flux2/apps/monitoring/kustomization.yaml
+++ b/flux2/apps/monitoring/kustomization.yaml
@@ -1,10 +1,10 @@
 ---
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: stackspin
 resources:
   - eventrouter-release.yaml
   - eventrouter-values-configmap.yaml
+  - kube-prometheus-stack-oauth-client.yaml
   - kube-prometheus-stack-release.yaml
   - kube-prometheus-stack-values-configmap.yaml
   - loki-configmap.yaml
diff --git a/flux2/apps/monitoring/loki-values-configmap.yaml b/flux2/apps/monitoring/loki-values-configmap.yaml
index 86ec319cb6148f990373e68d1513f94166ef188b..ca408a8ea2b0d927944d8457a13f35cfc33bf19b 100644
--- a/flux2/apps/monitoring/loki-values-configmap.yaml
+++ b/flux2/apps/monitoring/loki-values-configmap.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   name: stackspin-loki-values
+  namespace: stackspin
 data:
   values.yaml: |
     # https://github.com/grafana/helm-charts/blob/main/charts/loki/values.yaml
diff --git a/flux2/apps/monitoring/promtail-values-configmap.yaml b/flux2/apps/monitoring/promtail-values-configmap.yaml
index 83472dccb006b3465f184ee46bfc5357b1e1cf80..d32575cf5f1ab400642d730570fdfbc3dd6e3fe2 100644
--- a/flux2/apps/monitoring/promtail-values-configmap.yaml
+++ b/flux2/apps/monitoring/promtail-values-configmap.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   name: stackspin-promtail-values
+  namespace: stackspin
 data:
   values.yaml: |
     initContainer:
diff --git a/flux2/apps/monitoring/pvc.yaml b/flux2/apps/monitoring/pvc.yaml
index c8c46cda125d5f05fe64b8a31860468018086b09..4b96bcc6b6ffb9ada60c0916911760c56b9ebf9f 100644
--- a/flux2/apps/monitoring/pvc.yaml
+++ b/flux2/apps/monitoring/pvc.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: grafana
+  namespace: stackspin
 spec:
   accessModes:
     - ReadWriteOnce
diff --git a/flux2/apps/nextcloud/kustomization.yaml b/flux2/apps/nextcloud/kustomization.yaml
index 340146ce7c862b8e99469257a160897e925a967a..a46b0931464dfef09902a2110b0de97a064fa4e4 100644
--- a/flux2/apps/nextcloud/kustomization.yaml
+++ b/flux2/apps/nextcloud/kustomization.yaml
@@ -1,7 +1,7 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: stackspin-apps
 resources:
   - pvc.yaml
   - release.yaml
+  - nextcloud-oauth-client.yaml
   - nextcloud-values-configmap.yaml
diff --git a/flux2/apps/nextcloud/nextcloud-oauth-client.yaml b/flux2/apps/nextcloud/nextcloud-oauth-client.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1c81ce29b41ba8643e9f39486d2cab508c21cb98
--- /dev/null
+++ b/flux2/apps/nextcloud/nextcloud-oauth-client.yaml
@@ -0,0 +1,21 @@
+apiVersion: hydra.ory.sh/v1alpha1
+kind: OAuth2Client
+metadata:
+  name: nextcloud-oauth-client
+  # Has to live in the same namespace as the stackspin-wordpress-oauth-variables
+  # secret
+  namespace: flux-system
+spec:
+  grantTypes:
+    - authorization_code
+    - refresh_token
+    - client_credentials
+  responseTypes:
+    - id_token
+    - code
+  scope: "openid profile email stackspin_roles"
+  secretName: stackspin-nextcloud-oauth-variables
+  # these are optional
+  redirectUris:
+    - https://files.${domain}/apps/sociallogin/custom_oidc/stackspin
+  tokenEndpointAuthMethod: client_secret_post
diff --git a/flux2/apps/nextcloud/nextcloud-values-configmap.yaml b/flux2/apps/nextcloud/nextcloud-values-configmap.yaml
index 48dddc5a425a1bcbb4df591a09f35b094fb6f29f..63357ab60299f63624023d6950041de15b5e0e73 100644
--- a/flux2/apps/nextcloud/nextcloud-values-configmap.yaml
+++ b/flux2/apps/nextcloud/nextcloud-values-configmap.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   name: stackspin-nextcloud-values
+  namespace: stackspin-apps
 data:
   values.yaml: |
     nextcloud:
@@ -197,7 +198,7 @@ data:
         authorizeUrl: "https://sso.${domain}/oauth2/auth"
         tokenUrl: "https://sso.${domain}/oauth2/token"
         userInfoUrl: "https://sso.${domain}/userinfo"
-        clientSecret: "${nextcloud_oauth_client_secret}"
+        clientSecret: "${client_secret}"
         groupsClaim: "stackspin_roles"
         clientId: nextcloud
         scope: "openid profile email stackspin_roles"
diff --git a/flux2/apps/nextcloud/pvc.yaml b/flux2/apps/nextcloud/pvc.yaml
index 1b5a811439ecc80e2a6e0d0907fdf2ed69c7740b..76adebaebe8cb9924530e5b865d268698368a944 100644
--- a/flux2/apps/nextcloud/pvc.yaml
+++ b/flux2/apps/nextcloud/pvc.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: nextcloud-files
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
@@ -16,6 +17,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: nextcloud-mariadb
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
@@ -29,6 +31,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: nextcloud-postgresql
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
@@ -42,6 +45,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: nextcloud-onlyoffice-data
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
diff --git a/flux2/apps/wekan/kustomization.yaml b/flux2/apps/wekan/kustomization.yaml
index 0cebe4f023593cb83caf64c2b15048684ae2f0f7..f97ed1d649c472e7adc6a3c60b4d7a30af531f04 100644
--- a/flux2/apps/wekan/kustomization.yaml
+++ b/flux2/apps/wekan/kustomization.yaml
@@ -1,8 +1,8 @@
 ---
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: stackspin-apps
 resources:
   - pvc.yaml
   - release.yaml
+  - wekan-oauth-client.yaml
   - wekan-values-configmap.yaml
diff --git a/flux2/apps/wekan/pvc.yaml b/flux2/apps/wekan/pvc.yaml
index 71433b596223a7b7b19fc70cea189ef508f4d383..7114d1ec8f939b52598a271fdc1f01d9f73d7100 100644
--- a/flux2/apps/wekan/pvc.yaml
+++ b/flux2/apps/wekan/pvc.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: wekan
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
diff --git a/flux2/apps/wekan/wekan-oauth-client.yaml b/flux2/apps/wekan/wekan-oauth-client.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..979b74fe5c6086bff617cc0a860f4bdcd3abda10
--- /dev/null
+++ b/flux2/apps/wekan/wekan-oauth-client.yaml
@@ -0,0 +1,23 @@
+apiVersion: hydra.ory.sh/v1alpha1
+kind: OAuth2Client
+metadata:
+  name: wekan-oauth-client
+  # Has to live in the same namespace as the stackspin-wordpress-oauth-variables
+  # secret
+  namespace: flux-system
+spec:
+  # https://github.com/wekan/wekan/wiki/Keycloak
+  grantTypes:
+    - authorization_code
+    - refresh_token
+    - client_credentials
+    - implicit
+  responseTypes:
+    - id_token
+    - code
+  scope: "openid profile email stackspin_roles"
+  secretName: stackspin-wekan-oauth-variables
+  # these are optional
+  redirectUris:
+    - https://wekan.${domain}/_oauth/oidc
+  tokenEndpointAuthMethod: client_secret_post
diff --git a/flux2/apps/wekan/wekan-values-configmap.yaml b/flux2/apps/wekan/wekan-values-configmap.yaml
index 4bbe8a913b7d8b367dd03213ceedcccf6edc84f6..96eebee2ab6adc5694efdcf3a201150cae3a2552 100644
--- a/flux2/apps/wekan/wekan-values-configmap.yaml
+++ b/flux2/apps/wekan/wekan-values-configmap.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   name: stackspin-wekan-values
+  namespace: stackspin-apps
 data:
   values.yaml: |
     # https://github.com/wekan/wekan/blob/master/helm/wekan/values.yaml
@@ -54,7 +55,7 @@ data:
       - name: "MAIL_URL"
         value: "smtps://${outgoing_mail_smtp_user}:${outgoing_mail_smtp_password}@${outgoing_mail_smtp_host}:${outgoing_mail_smtp_port}"
       - name: "OAUTH2_SECRET"
-        value: "${wekan_oauth_client_secret}"
+        value: "${client_secret}"
       - name: "MONGO_URL"
         value: "mongodb://wekan:${mongodb_password}@wekan-mongodb:27017/wekan"
     service:
diff --git a/flux2/apps/wordpress/kustomization.yaml b/flux2/apps/wordpress/kustomization.yaml
index 5080ac0cd282652c3ce7b056cfba0fc199ca29d8..2613c916026cd310ed3ee077d35b71d39eef4b8e 100644
--- a/flux2/apps/wordpress/kustomization.yaml
+++ b/flux2/apps/wordpress/kustomization.yaml
@@ -1,8 +1,8 @@
 ---
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: stackspin-apps
 resources:
   - pvc.yaml
   - release.yaml
   - wordpress-values-configmap.yaml
+  - wordpress-oauth-client.yaml
diff --git a/flux2/apps/wordpress/pvc.yaml b/flux2/apps/wordpress/pvc.yaml
index adafcae0b7e03a46bcbd7b3ab5992ad5179df5c2..ce24beb09283dcc433fb9d649303f0555d733ff4 100644
--- a/flux2/apps/wordpress/pvc.yaml
+++ b/flux2/apps/wordpress/pvc.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: wordpress-files
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
@@ -16,6 +17,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: wordpress-mariadb
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
@@ -23,4 +25,4 @@ spec:
   resources:
     requests:
       storage: 512Mi
-  storageClassName: local-path
\ No newline at end of file
+  storageClassName: local-path
diff --git a/flux2/apps/wordpress/wordpress-oauth-client.yaml b/flux2/apps/wordpress/wordpress-oauth-client.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e3081e6c6a1141253cec60783503926eade4c9bb
--- /dev/null
+++ b/flux2/apps/wordpress/wordpress-oauth-client.yaml
@@ -0,0 +1,22 @@
+apiVersion: hydra.ory.sh/v1alpha1
+kind: OAuth2Client
+metadata:
+  name: wordpress-oauth-client
+  # Has to live in the same namespace as the stackspin-wordpress-oauth-variables
+  # secret
+  namespace: flux-system
+spec:
+  grantTypes:
+    - authorization_code
+    - refresh_token
+    - client_credentials
+    - implicit
+  responseTypes:
+    - id_token
+    - code
+  scope: "openid profile email stackspin_roles offline_access"
+  secretName: stackspin-wordpress-oauth-variables
+  # these are optional
+  redirectUris:
+    - https://www.${domain}/wp-admin/admin-ajax.php?action=openid-connect-authorize
+  tokenEndpointAuthMethod: client_secret_post
diff --git a/flux2/apps/wordpress/wordpress-values-configmap.yaml b/flux2/apps/wordpress/wordpress-values-configmap.yaml
index a8f426cab71a5196002fbc53410d56ef8dec4a26..db2261fd8e698e8bf00b4a3c0dff32e6b5d61ed0 100644
--- a/flux2/apps/wordpress/wordpress-values-configmap.yaml
+++ b/flux2/apps/wordpress/wordpress-values-configmap.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   name: stackspin-wordpress-values
+  namespace: stackspin-apps
 data:
   values.yaml: |
     wordpress:
@@ -25,7 +26,7 @@ data:
 
     openid_connect_settings:
       enabled: true
-      client_secret: ${wordpress_oauth_client_secret}
+      client_secret: ${client_secret}
       endpoint_login: https://sso.${domain}/oauth2/auth
       endpoint_userinfo: https://sso.${domain}/userinfo
       endpoint_token: https://sso.${domain}/oauth2/token
diff --git a/flux2/apps/zulip/kustomization.yaml b/flux2/apps/zulip/kustomization.yaml
index 6931340381a6ce8b17278d7481e4d2e071f88fa0..b4440818854b2c037c5b4e6f5490380faf4b1b40 100644
--- a/flux2/apps/zulip/kustomization.yaml
+++ b/flux2/apps/zulip/kustomization.yaml
@@ -1,9 +1,9 @@
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: stackspin-apps
 resources:
   - release.yaml
   - zulip-data-pvc.yaml
+  - zulip-oauth-client.yaml
   - zulip-postgres-pvc.yaml
   - zulip-redis-pvc.yaml
   - zulip-values-configmap.yaml
diff --git a/flux2/apps/zulip/zulip-data-pvc.yaml b/flux2/apps/zulip/zulip-data-pvc.yaml
index bea64b1ccd5d8c3945db37bfe9a1b7a5e88f7abb..19fb676fc2666855d407fde9f289853faab5f572 100644
--- a/flux2/apps/zulip/zulip-data-pvc.yaml
+++ b/flux2/apps/zulip/zulip-data-pvc.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: zulip-data
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
diff --git a/flux2/apps/zulip/zulip-oauth-client.yaml b/flux2/apps/zulip/zulip-oauth-client.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..299a2a5989a0060712852bb7b3fd5412842c4c64
--- /dev/null
+++ b/flux2/apps/zulip/zulip-oauth-client.yaml
@@ -0,0 +1,22 @@
+apiVersion: hydra.ory.sh/v1alpha1
+kind: OAuth2Client
+metadata:
+  name: zulip-oauth-client
+  # Has to live in the same namespace as the stackspin-wordpress-oauth-variables
+  # secret
+  namespace: flux-system
+spec:
+  # https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#openid-connect
+  grantTypes:
+    - authorization_code
+    - refresh_token
+    - client_credentials
+  responseTypes:
+    - id_token
+    - code
+  scope: "openid profile email stackspin_roles"
+  secretName: stackspin-zulip-oauth-variables
+  # these are optional
+  redirectUris:
+    - https://zulip.${domain}/complete/oidc/
+  tokenEndpointAuthMethod: client_secret_post
diff --git a/flux2/apps/zulip/zulip-postgres-pvc.yaml b/flux2/apps/zulip/zulip-postgres-pvc.yaml
index c094878204ee856477326d41728a42d8f083b45f..34e56936996f4c0ac05f6b57ac854265243e099d 100644
--- a/flux2/apps/zulip/zulip-postgres-pvc.yaml
+++ b/flux2/apps/zulip/zulip-postgres-pvc.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: zulip-postgres
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
diff --git a/flux2/apps/zulip/zulip-redis-pvc.yaml b/flux2/apps/zulip/zulip-redis-pvc.yaml
index 6890704a47c11be6c760946e2d74da80a8318e1f..edf0bad90493e69d53a69abe15d151ff2bdc9342 100644
--- a/flux2/apps/zulip/zulip-redis-pvc.yaml
+++ b/flux2/apps/zulip/zulip-redis-pvc.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: zulip-redis
+  namespace: stackspin-apps
 spec:
   accessModes:
     - ReadWriteOnce
diff --git a/flux2/apps/zulip/zulip-values-configmap.yaml b/flux2/apps/zulip/zulip-values-configmap.yaml
index 53e8dd3642949da1138ef59a92d7ac9867eb823a..80d1b57b8a365df483c6776132d4419382309efd 100644
--- a/flux2/apps/zulip/zulip-values-configmap.yaml
+++ b/flux2/apps/zulip/zulip-values-configmap.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   name: stackspin-zulip-values
+  namespace: stackspin-apps
 data:
   values.yaml: |
     image:
@@ -85,7 +86,7 @@ data:
         # (https://github.com/greenhost/docker-zulip/commit/d583a2d28707a3b77bf610bedc2c2bb81f2a5f88)
         # NOTE: This is a Python object, not JSON
         SETTING_SOCIAL_AUTH_OIDC_ENABLED_IDPS: '{"stackspin": { "oidc_url": "https://sso.${domain}/", "display_name": "Stackspin", "display_icon": None, "client_id": "zulip", "secret": get_secret("social_auth_oidc_secret"), "auto_signup": True }}'
-        SECRETS_social_auth_oidc_secret: "${zulip_oauth_client_secret}"
+        SECRETS_social_auth_oidc_secret: "${client_secret}"
         # Enable "low memory mode", queue workers run 1 multithreaded process
         QUEUE_WORKERS_MULTIPROCESS: 'False'
       resources:
diff --git a/flux2/cluster/base/dashboard.yaml b/flux2/cluster/base/dashboard.yaml
index eb0aebbc49fe14e179b37ae2da43d1a7d8e28fc4..f7bf842aa84d76f56e85db2eb45ceae5ce16c3ee 100644
--- a/flux2/cluster/base/dashboard.yaml
+++ b/flux2/cluster/base/dashboard.yaml
@@ -20,7 +20,7 @@ spec:
       - kind: Secret
         name: stackspin-dashboard-variables
       - kind: Secret
-        name: stackspin-oauth-variables
+        name: stackspin-dashboard-oauth-variables
       - kind: Secret
         name: stackspin-cluster-variables
   healthChecks:
diff --git a/flux2/cluster/base/single-sign-on.yaml b/flux2/cluster/base/single-sign-on.yaml
index e5efa49f8da028aa6d17f25a9203b03c7a3f96b4..711a56d589c93ed9621ef9c63618dff3c8077436 100644
--- a/flux2/cluster/base/single-sign-on.yaml
+++ b/flux2/cluster/base/single-sign-on.yaml
@@ -19,8 +19,6 @@ spec:
     substituteFrom:
       - kind: Secret
         name: stackspin-single-sign-on-variables
-      - kind: Secret
-        name: stackspin-oauth-variables
       - kind: Secret
         name: stackspin-cluster-variables
   healthChecks:
diff --git a/flux2/cluster/optional/monitoring/monitoring.yaml b/flux2/cluster/optional/monitoring/monitoring.yaml
index 95e69c6e44e1f380ed5e70111c34ee60278f9a09..88fa3538a3a807e35696ec60ea532f95cd6c586f 100644
--- a/flux2/cluster/optional/monitoring/monitoring.yaml
+++ b/flux2/cluster/optional/monitoring/monitoring.yaml
@@ -10,6 +10,7 @@ spec:
   dependsOn:
     - name: nginx
     - name: local-path-provisioner
+    - name: single-sign-on
   sourceRef:
     kind: GitRepository
     name: stackspin
@@ -61,6 +62,6 @@ spec:
       - kind: Secret
         name: stackspin-kube-prometheus-stack-variables
       - kind: Secret
-        name: stackspin-oauth-variables
+        name: stackspin-kube-prometheus-stack-oauth-variables
       - kind: Secret
         name: stackspin-cluster-variables
diff --git a/flux2/cluster/optional/nextcloud/nextcloud.yaml b/flux2/cluster/optional/nextcloud/nextcloud.yaml
index 3c260934f75957c2bb9c2f4a3856b4ffdefaefda..a0aecb83d45ef5694e317b78e7205e319b2c679c 100644
--- a/flux2/cluster/optional/nextcloud/nextcloud.yaml
+++ b/flux2/cluster/optional/nextcloud/nextcloud.yaml
@@ -10,6 +10,7 @@ spec:
   dependsOn:
     - name: nginx
     - name: local-path-provisioner
+    - name: single-sign-on
   sourceRef:
     kind: GitRepository
     name: stackspin
@@ -33,6 +34,6 @@ spec:
       - kind: Secret
         name: stackspin-nextcloud-variables
       - kind: Secret
-        name: stackspin-oauth-variables
+        name: stackspin-nextcloud-oauth-variables
       - kind: Secret
         name: stackspin-cluster-variables
diff --git a/flux2/cluster/optional/wekan/wekan.yaml b/flux2/cluster/optional/wekan/wekan.yaml
index d4b13714a75a5f25639026839578126cae960498..76150ba75b6436d1c72435f76fbeaa1df8016fdb 100644
--- a/flux2/cluster/optional/wekan/wekan.yaml
+++ b/flux2/cluster/optional/wekan/wekan.yaml
@@ -10,6 +10,7 @@ spec:
   dependsOn:
     - name: nginx
     - name: local-path-provisioner
+    - name: single-sign-on
   sourceRef:
     kind: GitRepository
     name: stackspin
@@ -29,6 +30,6 @@ spec:
       - kind: Secret
         name: stackspin-wekan-variables
       - kind: Secret
-        name: stackspin-oauth-variables
+        name: stackspin-wekan-oauth-variables
       - kind: Secret
         name: stackspin-cluster-variables
diff --git a/flux2/cluster/optional/wordpress/wordpress.yaml b/flux2/cluster/optional/wordpress/wordpress.yaml
index 8b5fc60ee5d84bf5c5457e1d8695337d57c26335..8c5f4b4de4c618d495c02f372800c376c5a2133f 100644
--- a/flux2/cluster/optional/wordpress/wordpress.yaml
+++ b/flux2/cluster/optional/wordpress/wordpress.yaml
@@ -10,6 +10,7 @@ spec:
   dependsOn:
     - name: nginx
     - name: local-path-provisioner
+    - name: single-sign-on
   sourceRef:
     kind: GitRepository
     name: stackspin
@@ -29,6 +30,6 @@ spec:
       - kind: Secret
         name: stackspin-wordpress-variables
       - kind: Secret
-        name: stackspin-oauth-variables
+        name: stackspin-wordpress-oauth-variables
       - kind: Secret
         name: stackspin-cluster-variables
diff --git a/flux2/cluster/optional/zulip/zulip.yaml b/flux2/cluster/optional/zulip/zulip.yaml
index c72a56244d721d6bf2849564ee250bc9e29f81d4..4cb36eccd4296085ab28e44328bd7dcd565443b3 100644
--- a/flux2/cluster/optional/zulip/zulip.yaml
+++ b/flux2/cluster/optional/zulip/zulip.yaml
@@ -10,6 +10,7 @@ spec:
   dependsOn:
     - name: nginx
     - name: local-path-provisioner
+    - name: single-sign-on
   sourceRef:
     kind: GitRepository
     name: stackspin
@@ -45,6 +46,6 @@ spec:
       - kind: Secret
         name: stackspin-zulip-variables
       - kind: Secret
-        name: stackspin-oauth-variables
+        name: stackspin-zulip-oauth-variables
       - kind: Secret
         name: stackspin-cluster-variables
diff --git a/flux2/core/base/dashboard/dashboard-oauth-client.yaml b/flux2/core/base/dashboard/dashboard-oauth-client.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f77c6209706b65cb78a92562ac98e3c246ed0132
--- /dev/null
+++ b/flux2/core/base/dashboard/dashboard-oauth-client.yaml
@@ -0,0 +1,22 @@
+apiVersion: hydra.ory.sh/v1alpha1
+kind: OAuth2Client
+metadata:
+  name: dashboard-oauth-client
+  # Has to live in the same namespace as the stackspin-wordpress-oauth-variables
+  # secret
+  namespace: flux-system
+spec:
+  grantTypes:
+    - authorization_code
+    - refresh_token
+    - client_credentials
+    - implicit
+  responseTypes:
+    - id_token
+    - code
+  scope: "openid profile email stackspin_roles"
+  secretName: stackspin-dashboard-oauth-variables
+  # these are optional
+  redirectUris:
+    - https://dashboard.${domain}/_oauth/oidc
+  tokenEndpointAuthMethod: client_secret_post
diff --git a/flux2/core/base/dashboard/dashboard-release.yaml b/flux2/core/base/dashboard/dashboard-release.yaml
index 0f1e3447446749dbe6a11543a18099c0f9952f95..3dadb255f8fc70545d76d2a66f83ed44db620b99 100644
--- a/flux2/core/base/dashboard/dashboard-release.yaml
+++ b/flux2/core/base/dashboard/dashboard-release.yaml
@@ -3,6 +3,7 @@ apiVersion: helm.toolkit.fluxcd.io/v2beta1
 kind: HelmRelease
 metadata:
   name: dashboard
+  namespace: stackspin
 spec:
   releaseName: dashboard
   dependsOn:
diff --git a/flux2/core/base/dashboard/dashboard-values-configmap.yaml b/flux2/core/base/dashboard/dashboard-values-configmap.yaml
index 1948aa445bbd2e37da03755e1754c41b15b27079..df64435150e69c7a9faf0c35744111d5a5ae55a5 100644
--- a/flux2/core/base/dashboard/dashboard-values-configmap.yaml
+++ b/flux2/core/base/dashboard/dashboard-values-configmap.yaml
@@ -3,6 +3,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   name: stackspin-dashboard-values
+  namespace: stackspin
 data:
   values.yaml: |
     fullnameOverride: dashboard
diff --git a/flux2/core/base/dashboard/kustomization.yaml b/flux2/core/base/dashboard/kustomization.yaml
index 93dd5096287c3350486e636d8b0ad98285569ca8..9989b257fd7908e49c38f5e1276e73cb523f0c4a 100644
--- a/flux2/core/base/dashboard/kustomization.yaml
+++ b/flux2/core/base/dashboard/kustomization.yaml
@@ -1,7 +1,7 @@
 ---
 apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
-namespace: stackspin
 resources:
-  - ./dashboard-release.yaml
-  - ./dashboard-values-configmap.yaml
+  - dashboard-oauth-client.yaml
+  - dashboard-release.yaml
+  - dashboard-values-configmap.yaml
diff --git a/flux2/core/base/single-sign-on/single-sign-on-values-configmap.yaml b/flux2/core/base/single-sign-on/single-sign-on-values-configmap.yaml
index be6e45347d7df6f2ba073daeed31d7d2bea42d64..2556b1ffc1c7ae6d36271a3c50cb541a09f79f0b 100644
--- a/flux2/core/base/single-sign-on/single-sign-on-values-configmap.yaml
+++ b/flux2/core/base/single-sign-on/single-sign-on-values-configmap.yaml
@@ -25,7 +25,14 @@ data:
           CREATE DATABASE hydra WITH OWNER hydra;
           CREATE DATABASE stackspin WITH OWNER stackspin;
 
+    hydra-maester:
+      # Watches the flux-system namespace because that is where the app
+      # installation secrets live
+      enabledNamespaces:
+        - flux-system
     hydra:
+      maester:
+        enabled: true
       hydra:
         config:
           urls:
@@ -85,87 +92,3 @@ data:
               # be on this link:
               registration:
                 ui_url: https://sso.${domain}/login/registration
-
-
-    oAuthClients:
-    - clientName: nextcloud
-      clientSecret: "${nextcloud_oauth_client_secret}"
-      redirectUri: "https://files.${domain}/apps/sociallogin/custom_oidc/stackspin"
-      scopes: "openid profile email stackspin_roles"
-      clientUri: "https://files.${domain}"
-      clientLogoUri: "https://files.${domain}/core/img/favicon-touch.png"
-      tokenEndpointAuthMethod: "client_secret_post"
-      responseTypes:
-        - "code"
-        - "id_token"
-      grantTypes:
-        - "authorization_code"
-        - "refresh_token"
-        - "client_credentials"
-    - clientName: wordpress
-      clientSecret: "${wordpress_oauth_client_secret}"
-      redirectUri: "https://www.${domain}/wp-admin/admin-ajax.php?action=openid-connect-authorize"
-      scopes: "openid profile email stackspin_roles offline_access"
-      clientUri: "https://www.${domain}"
-      clientLogoUri: "https://www.${domain}/wp-admin/images/wordpress-logo.svg"
-      tokenEndpointAuthMethod: "client_secret_post"
-      responseTypes:
-        - "code"
-        - "id_token"
-      grantTypes:
-        - "authorization_code"
-        - "refresh_token"
-        - "client_credentials"
-        - "implicit"
-    - clientName: grafana
-      clientSecret: "${grafana_oauth_client_secret}"
-      redirectUri: "https://grafana.${domain}/login/generic_oauth"
-      scopes: "openid profile email stackspin_roles"
-      clientUri: "https://grafana.${domain}"
-      clientLogoUri: "https://grafana.${domain}/public/img/grafana_icon.svg"
-      tokenEndpointAuthMethod: "client_secret_post"
-      responseTypes:
-        - "code"
-        - "id_token"
-      grantTypes:
-        - "authorization_code"
-        - "refresh_token"
-        - "client_credentials"
-    # https://github.com/wekan/wekan/wiki/Keycloak
-    - clientName: wekan
-      clientSecret: "${wekan_oauth_client_secret}"
-      redirectUri: "https://wekan.${domain}/_oauth/oidc"
-      scopes: "openid profile email"
-      clientUri: "https://wekan.${domain}"
-      clientLogoUri: "https://wekan.${domain}/wekan-logo.svg"
-      tokenEndpointAuthMethod: "client_secret_post"
-      responseTypes:
-        - "code"
-        - "id_token"
-      grantTypes:
-        - "authorization_code"
-        - "refresh_token"
-        - "client_credentials"
-        - "implicit"
-    # https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#openid-connect
-    - clientName: zulip
-      clientSecret: "${zulip_oauth_client_secret}"
-      redirectUri: "https://zulip.${domain}/complete/oidc/"
-      scopes: "openid profile email"
-      clientUri: "https://zulip.${domain}"
-      clientLogoUri: "https://zulip.${domain}/static/images/zulip-logo.svg"
-    - clientName: dashboard
-      clientSecret: "${dashboard_oauth_client_secret}"
-      redirectUri: "https://dashboard.${domain}/_oauth/oidc"
-      scopes: "openid profile email"
-      clientUri: "https://dashboard.${domain}"
-      clientLogoUri: "https://dashboard.${domain}/assets/logo.svg"
-      tokenEndpointAuthMethod: "client_secret_post"
-      responseTypes:
-        - "code"
-        - "id_token"
-      grantTypes:
-        - "authorization_code"
-        - "refresh_token"
-        - "client_credentials"
-        - "implicit"
diff --git a/install/generate_secrets.py b/install/generate_secrets.py
index 56666eaf24c2f2e7f869df1a1efaef13888f50ad..3640fd75d388c12aebd4134318a5121c008f501a 100644
--- a/install/generate_secrets.py
+++ b/install/generate_secrets.py
@@ -25,6 +25,14 @@ from kubernetes import client, config
 from kubernetes.client.exceptions import ApiException
 from kubernetes.utils import create_from_yaml
 
+# This script gets called with an app name as argument. Most of them need an
+# oauth client in Hydra, but some don't. This list contains the ones that
+# don't.
+APPS_WITHOUT_OAUTH = [
+    "single-sign-on",
+    "prometheus",
+    "alertmanager",
+]
 
 def main():
     """Run everything"""
@@ -37,7 +45,11 @@ def main():
         sys.exit(1)
     app_name = sys.argv[1]
 
-    create_variables_secret(app_name, env)
+    # Create app variables secret
+    create_variables_secret(app_name, f"stackspin-{app_name}-variables.yaml.jinja", env)
+    # Create a secret that contains the oauth variables for Hydra Maester
+    if app_name not in APPS_WITHOUT_OAUTH:
+        create_variables_secret(app_name, "stackspin-oauth-variables.yaml.jinja", env)
     create_basic_auth_secret(app_name, env)
 
 
@@ -47,16 +59,20 @@ def get_templates_dir():
     return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates')
 
 
-def create_variables_secret(app_name, env):
+def create_variables_secret(app_name, variables_filename, env):
     """Checks if a variables secret for app_name already exists, generates it if necessary"""
-    variables_filename = \
-        os.path.join(get_templates_dir(), f"stackspin-{app_name}-variables.yaml.jinja")
-    if os.path.exists(variables_filename):
+    variables_filepath = \
+        os.path.join(get_templates_dir(), variables_filename)
+    if os.path.exists(variables_filepath):
         # Check if k8s secret already exists, if not, generate it
-        with open(variables_filename) as template_file:
+        with open(variables_filepath) as template_file:
             lines = template_file.read()
             secret_name, secret_namespace = get_secret_metadata(lines)
-            new_secret_dict = yaml.safe_load(env.from_string(lines).render())
+            new_secret_dict = yaml.safe_load(
+                env.from_string(
+                    lines,
+                    globals={"app": app_name}
+                ).render())
             current_secret_data = get_kubernetes_secret_data(secret_name,
                     secret_namespace)
             if current_secret_data is None:
@@ -79,7 +95,7 @@ def create_variables_secret(app_name, env):
             store_kubernetes_secret(new_secret_dict, secret_namespace,
                                     update=update_secret)
     else:
-        print(f'File {variables_filename} does not exist, no action needed')
+        print(f'Template {variables_filename} does not exist, no action needed')
 
 
 def create_basic_auth_secret(app_name, env):
diff --git a/install/install-stackspin.sh b/install/install-stackspin.sh
index d913734f3db48e515e32c37e9b9232e34efab1b8..dacde30341b765b95a5a631193ca631e50a5d04e 100755
--- a/install/install-stackspin.sh
+++ b/install/install-stackspin.sh
@@ -37,10 +37,9 @@ echo "Tracking branch $branch for https://open.greenhost.net/stackspin/stackspin
 kubectl get namespace stackspin 2>/dev/null || kubectl create namespace stackspin
 kubectl get namespace stackspin-apps 2>/dev/null || kubectl create namespace stackspin-apps
 
-# Generate oauth and SSO secrets
+# Generate dashboard and SSO secrets
 python "$(dirname "$0")/generate_secrets.py" dashboard
 python "$(dirname "$0")/generate_secrets.py" single-sign-on
-python "$(dirname "$0")/generate_secrets.py" oauth
 
 # Generate secrets for monitoring
 python "$(dirname "$0")/generate_secrets.py" kube-prometheus-stack
diff --git a/install/templates/stackspin-oauth-variables.yaml.jinja b/install/templates/stackspin-oauth-variables.yaml.jinja
index 66445dd80fe4c04d15d5f39be141a5b03c6306f0..32a0ab0785d45ed113a39706af4b1c4f8601e080 100644
--- a/install/templates/stackspin-oauth-variables.yaml.jinja
+++ b/install/templates/stackspin-oauth-variables.yaml.jinja
@@ -2,12 +2,7 @@
 apiVersion: v1
 kind: Secret
 metadata:
-  name: stackspin-oauth-variables
+  name: stackspin-{{ app }}-oauth-variables
 data:
-  grafana_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
-  nextcloud_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
-  userpanel_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
-  wekan_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
-  wordpress_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
-  zulip_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
-  dashboard_oauth_client_secret: "{{ 32 | generate_password | b64encode }}"
+  client_id: "{{ app | b64encode }}"
+  client_secret: "{{ 32 | generate_password | b64encode }}"