From b0023fef922cf5834cb48b32de2f0c8849306815 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 16 Mar 2026 21:50:34 -0700 Subject: [PATCH] Switch Mealie OIDC to confidential client Mealie requires OIDC_CLIENT_SECRET even though its docs say "public client with PKCE". The token exchange happens server-side in Mealie's Python backend, so the secret never reaches the browser. - Generate client secret, store in 1Password - Add to Authentik external-secret and worker env - Switch blueprint from public to confidential - Add ExternalSecret for mealie namespace - Update docs to reflect confidential client Co-Authored-By: Claude Opus 4.6 (1M context) --- .../authentik/configmap-blueprint.yaml | 5 +++-- .../authentik/deployment-worker.yaml | 5 +++++ .../manifests/authentik/external-secret.yaml | 4 ++++ argocd/manifests/mealie/deployment.yaml | 5 +++++ argocd/manifests/mealie/external-secret.yaml | 19 +++++++++++++++++++ argocd/manifests/mealie/kustomization.yaml | 1 + docs/reference/services/mealie.md | 4 ++-- 7 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 argocd/manifests/mealie/external-secret.yaml diff --git a/argocd/manifests/authentik/configmap-blueprint.yaml b/argocd/manifests/authentik/configmap-blueprint.yaml index 285bb82..cc3ff43 100644 --- a/argocd/manifests/authentik/configmap-blueprint.yaml +++ b/argocd/manifests/authentik/configmap-blueprint.yaml @@ -353,7 +353,7 @@ data: labels: blueprints.goauthentik.io/description: "Mealie OIDC provider and application" entries: - # OAuth2 provider for Mealie (public client — Mealie uses PKCE) + # OAuth2 provider for Mealie (confidential — Mealie requires client_secret) - model: authentik_providers_oauth2.oauth2provider id: mealie-provider identifiers: @@ -362,8 +362,9 @@ data: name: Mealie 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: public + client_type: confidential client_id: mealie + client_secret: !Env AUTHENTIK_MEALIE_CLIENT_SECRET redirect_uris: - matching_mode: strict url: https://meals.ops.eblu.me/login diff --git a/argocd/manifests/authentik/deployment-worker.yaml b/argocd/manifests/authentik/deployment-worker.yaml index 2b341bf..3d4fb0c 100644 --- a/argocd/manifests/authentik/deployment-worker.yaml +++ b/argocd/manifests/authentik/deployment-worker.yaml @@ -78,6 +78,11 @@ spec: secretKeyRef: name: authentik-config key: argocd-client-secret + - name: AUTHENTIK_MEALIE_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: authentik-config + key: mealie-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 495eda8..fb22f2b 100644 --- a/argocd/manifests/authentik/external-secret.yaml +++ b/argocd/manifests/authentik/external-secret.yaml @@ -57,3 +57,7 @@ spec: remoteRef: key: "Authentik (blumeops)" property: argocd-client-secret + - secretKey: mealie-client-secret + remoteRef: + key: "Authentik (blumeops)" + property: mealie-client-secret diff --git a/argocd/manifests/mealie/deployment.yaml b/argocd/manifests/mealie/deployment.yaml index d88d632..4eab901 100644 --- a/argocd/manifests/mealie/deployment.yaml +++ b/argocd/manifests/mealie/deployment.yaml @@ -36,6 +36,11 @@ spec: value: "https://authentik.ops.eblu.me/application/o/mealie/.well-known/openid-configuration" - name: OIDC_CLIENT_ID value: "mealie" + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: mealie-oidc + key: client-secret - name: OIDC_AUTO_REDIRECT value: "false" - name: OIDC_PROVIDER_NAME diff --git a/argocd/manifests/mealie/external-secret.yaml b/argocd/manifests/mealie/external-secret.yaml new file mode 100644 index 0000000..6a77c5d --- /dev/null +++ b/argocd/manifests/mealie/external-secret.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: mealie-oidc + namespace: mealie +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: onepassword-blumeops + target: + name: mealie-oidc + creationPolicy: Owner + data: + - secretKey: client-secret + remoteRef: + key: "Authentik (blumeops)" + property: mealie-client-secret diff --git a/argocd/manifests/mealie/kustomization.yaml b/argocd/manifests/mealie/kustomization.yaml index 4046658..0d1ee04 100644 --- a/argocd/manifests/mealie/kustomization.yaml +++ b/argocd/manifests/mealie/kustomization.yaml @@ -8,6 +8,7 @@ resources: - service.yaml - pvc.yaml - ingress-tailscale.yaml + - external-secret.yaml images: - name: registry.ops.eblu.me/blumeops/mealie diff --git a/docs/reference/services/mealie.md b/docs/reference/services/mealie.md index 4081291..f309a5e 100644 --- a/docs/reference/services/mealie.md +++ b/docs/reference/services/mealie.md @@ -30,11 +30,11 @@ Self-hosted recipe manager with a REST API. Part of the meal planning pipeline: - Built-in meal planning and shopping lists - Recipe import from URLs - API token auth for automation -- OIDC login via [[authentik]] (public client with PKCE) +- OIDC login via [[authentik]] (confidential client) ## Authentication -OIDC via [[authentik]] using a public client with PKCE (no client secret needed). All Authentik users can log in; members of the `admins` group get Mealie admin privileges via `OIDC_ADMIN_GROUP`. +OIDC via [[authentik]] using a confidential client. Client secret stored in 1Password (`Authentik (blumeops)` / `mealie-client-secret`) and delivered via ExternalSecret. All Authentik users can log in; members of the `admins` group get Mealie admin privileges via `OIDC_ADMIN_GROUP`. ## Storage