Skip to content

Example: the issuer generates a client's secret

Goal

Stop delivering a confidential client's secret by hand. The issuer makes it and keeps it at external/oidc/<client>. The relying party reads it with its own secret operator.

What you need

  • A confidential client.
  • A Secrets adapter that writes only if absent (ssm or openbao), and a State shared between replicas.

The policy snippet

clients:
  grafana:
    kind: confidential
    secret: {generate: true}   # was: secret: grafana-oidc
    requires: [devel:grafana:viewer]
    redirects: [/login/generic_oauth]

The exchange / command

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata: {name: grafana-oidc, namespace: grafana}
spec:
  secretStoreRef: {kind: ClusterSecretStore, name: sluis}
  target: {name: grafana-oidc}
  data:
    - secretKey: client-secret
      remoteRef:
        key: external/oidc/grafana
        property: client-secret

Once the relying party signs in with the generated value, delete the input clients/grafana/secret. The stored record takes precedence over it.

Verify

sluisctl clients show grafana prints created and generated in policy true. The audit trail shows roster.client.secret.created, or .adopted when an input secret existed. A sign-in to the relying party succeeds.

Undo

Put the name back in secret:; the stored record is reported orphaned and kept. Rotate with sluisctl clients rotate.

Recipe: Let the issuer generate a client's secret; rotation: rotate a client secret. The document is the oidc/v1 shape in secrets.

Snippet source: docs/guides/sluis/let-the-issuer-generate-a-clients-secret.md; policy/clientsecret_test.go holds the parser cases for the object form.