Argo CD signs people in through any OpenID Connect provider set in its argocd-cm
ConfigMap, without Dex. With Nauthera the client is an OIDCClient in the argocd
namespace, a public client with PKCE, so there is no client secret to deliver: the web UI
and the argocd CLI share it. Roles map by email, since Nauthera emits no groups claim.
Checked against: Argo CD 3.6.0 (released November 3, 2026), its
user management docs,
RBAC docs
and OIDC client code
at that tag, read on November 4, 2026, and the OIDCClient definition in nauthera-server.
This page has not recorded an end-to-end test against a running Argo CD.
1. Admit the namespace
The client configures the organization that admits the argocd namespace. For the main
organization that is the chart value operator., empty by
default:
# nauthera-server chart values
operator:
enabled: true
mainOrganizationNamespaces: [argocd]For another organization, add argocd to its Organization resource's
spec.namespaces; its issuer is then https:.
2. The OIDCClient
apiVersion: nauthera.io/v1alpha1
kind: OIDCClient
metadata:
name: argocd
namespace: argocd
spec:
clientId: argocd
displayName: Argo CD
redirectUris:
- https://argocd.example.com/auth/callback # the web UI
- http://localhost:8085/auth/callback # argocd login --sso
postLogoutRedirectUris:
- https://argocd.example.com
grantTypes: [authorization_code, refresh_token]
responseTypes: [code]
scopes: [openid, profile, email, offline_access]
tokenEndpointAuthMethod: none
pkcePolicy: required_s256
allowRefresh: true
deletionPolicy: RetaintokenEndpointAuthMethod: makes it a public client, which must use
required_s256. Nauthera matches redirect URIs exactly, so each one Argo CD sends is
listed: its url plus /auth/callback, and the CLI's loopback address on its default
--sso-port of 8085. Argo CD's Keycloak guide also lists /pkce/verify; in 3.6.0 the
server runs the PKCE exchange itself and returns to /auth/callback, and the web UI has
no /pkce/verify route
(UI routes).
3. argocd-cm
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
labels:
app.kubernetes.io/name: argocd-cm
app.kubernetes.io/part-of: argocd
data:
url: https://argocd.example.com
oidc.config: |
name: Nauthera
issuer: https://auth.example.com
clientID: argocd
enablePKCEAuthentication: true
requestedScopes: ["openid", "profile", "email", "offline_access"]
refreshTokenThreshold: 2m
logoutURL: https://auth.example.com/end_session?client_id=argocd&id_token_hint={{token}}&post_logout_redirect_uri={{logoutRedirectURL}}requestedScopesmust be set. Argo CD asks forgroupsby default, and Nauthera refuses a scope the client is not registered for withinvalid_scope.refreshTokenThresholdkeeps a session alive. Argo CD's session lasts as long as the ID token, and Nauthera's ID tokens last 10 minutes by default (issuer.id_token_ttl). Withoffline_accessandallowRefresh, Argo CD refreshes the token this long before it expires.logoutURLends the Nauthera session too. Argo CD fills in itsurl, without a trailing slash, which is whypostLogoutRedirectUrislists it that way. When the ID token in the hint has expired, Nauthera asks the user to confirm before signing out.rootCA: add the PEM of your CA here when Nauthera's certificate is not from a public one, as in a closed network.
4. Roles: argocd-rbac-cm
Nauthera's tokens carry no groups claim, so bind roles to email addresses. scopes
tells Argo CD which claims to match against policy subjects, in addition to sub:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
labels:
app.kubernetes.io/name: argocd-rbac-cm
app.kubernetes.io/part-of: argocd
data:
scopes: "[email]"
policy.csv: |
g, alice@example.com, role:admin
g, bob@example.com, role:readonlyWithout policy.default, a signed-in user no line names sees no applications. Bind by
email only where addresses are trustworthy: self-registration is off by default
(auth.), and where it is on, require a verified address before a
session starts: config. in the chart for the main
organization, spec. on an Organization. Binding by sub instead
is stable but less readable.
5. The CLI
argocd login argocd.example.com --ssoThe CLI opens the browser on Nauthera's sign-in page, runs the authorization code flow
with PKCE against the same client, and receives the code on http://localhost:8085. For
another port, pass --sso-port and register that redirect URI too. Nauthera asks for
consent on every sign-in through a loopback redirect, whoever registered the client,
because another program on the machine could listen on the same port. Sign-ins through
the web UI are not asked: an operator-registered client counts as approved by an
administrator.
Why not a confidential client and its Secret
The operator could generate a client secret, but Argo CD reads a $<secret>:<key>
reference only from a Secret labelled app., and the
operator labels the Secret it writes only with app.. A label you
add by hand survives the operator's updates, which change only the data and the
fingerprint annotation, but not a rotation: deleting the Secret to rotate it makes the
operator write a new one without the label. With PKCE, the public client needs no secret.
When something goes wrong
| What you see | Why | Fix |
|---|---|---|
The OIDCClient shows NamespaceNotAdmitted | No organization admits argocd | Step 1 |
Nauthera answers invalid_scope | Argo CD's default scopes include groups | Set requestedScopes |
Nauthera refuses the redirect_uri | It differs from every registered URI, character for character | Make url and redirectUris agree; register the CLI's port |
| Users sign in but see no applications | No policy line names them | Add their email to policy.csv, with scopes: "[email]" |
| Users are signed out after about ten minutes | The ID token expired and was not refreshed | offline_access, allowRefresh and refreshTokenThreshold |
x509: | Nauthera's certificate is from a private CA | rootCA in oidc.config; for the CLI, see Argo CD's FAQ |
| Logout ends on Nauthera's own page | post_logout_redirect_uri is not registered exactly | List url without a trailing slash in postLogoutRedirectUris |
Argo CD has no health check of its own for an OIDCClient, so it gives the resource no
health status and leaves it out of the application's health, whatever the operator
reports (health code). The
GitOps guide has
the health check that reads the operator's status instead.