Skip to content

The v1 docs follow nauthera-server's main branch and its 0.x releases on the way to 1.0, so fields and APIs can still change.

v1

OIDCClient

Docs version v1 (main)Nauthera is pre-1.0, in a design-partner programUpdated

An OIDCClient declares one OAuth 2.0 / OpenID Connect client. The operator sends its spec to the server's gRPC AdminService (UpsertOIDCClient), writes the server's answer to status, and delivers a generated client secret into a Kubernetes Secret.

PropertyValue
API group / versionnauthera.io/v1alpha1
KindOIDCClient
Plural / short nameoidcclients / oidcc
ScopeNamespaced
Definitiondeploy/crds/oidcclient-crd.yaml in nauthera-server

Example

apiVersion: nauthera.io/v1alpha1
kind: OIDCClient
metadata:
  name: example-webapp
  namespace: nauthera     # a namespace the organization admits
spec:
  clientId: example-webapp
  displayName: Example Web App
  redirectUris:
    - https://app.example.com/callback
    - https://app.example.com/silent-renew
  postLogoutRedirectUris:
    - https://app.example.com/
  grantTypes:
    - authorization_code
    - refresh_token
  responseTypes:
    - code
  scopes:
    - openid
    - profile
    - email
    - offline_access
  tokenEndpointAuthMethod: client_secret_basic
  pkcePolicy: required_s256
  allowRefresh: true
  accessTokenTtlSeconds: 900        # 15m
  idTokenTtlSeconds: 900            # 15m
  deletionPolicy: Retain
  secret:
    generate: true

The server applies all three token lifetimes. refreshTokenTtlSeconds may not exceed the deployment's issuer.refresh_token_ttl (a longer one is refused), and a client that sets none gets that lifetime.

The spec below comes from the resource definition, and two of its field descriptions are wrong. pkcePolicy: disabled does not reject PKCE. It behaves like optional: a code_challenge is accepted and verified if sent, but not required. A public client always needs PKCE, whatever its policy says. The idTokenSignedResponseAlg and pkcePolicy descriptions also say the operator surfaces a refused apply as a NotReady condition. There is no such condition: a refusal is the Ready condition with status False and reason Refused, and phase: Error (see Status).

How secrets work

The resource never holds a client secret. When secret.generate is true, the server generates a high-entropy secret and stores only its SHA-256 digest. It returns the plaintext once, in the response to the upsert that minted it. That is normally the upsert that created the client. An update that asks for a secret for a client without one (a public client made confidential, for example) also mints and returns one. Re-applying a spec never rotates the secret. Every other response carries only a non-reversible fingerprint (status.secretFingerprint).

The operator writes the plaintext into a Secret in the resource's namespace, named by spec.secret.secretName or, by default, after the resource. The Secret holds the keys client-id, client-secret and issuer, carries the fingerprint in its nauthera.io/secret-fingerprint annotation, and is recorded in status.secretRef. It is owned by the resource, and the operator never writes over a Secret it does not own; that case is reported as SecretUndeliverable.

To rotate the secret, delete the Secret. The operator mints a new secret, writes it again and records a SecretRotated event with both fingerprints. Workloads that read the old secret must reload it.

A secret you supply

spec.secret.secretRef takes the secret from a Secret you hold instead, such as one a SealedSecret or the External Secrets operator produces, or one an application outside the cluster also uses:

spec:
  tokenEndpointAuthMethod: client_secret_basic
  secret:
    secretRef:
      name: storefront-oidc
      key: client-secret        # the default

The operator reads the value and sends it; the server stores only its digest and reports its fingerprint in status.secretFingerprint, as for a minted one. The operator never writes or owns that Secret. Editing it sends the new value, which replaces the old one at once, with no overlap. The value must be at least 43 printable ASCII characters with no whitespace, drawn at random (openssl rand -base64 48 | tr -d '\n'); the server refuses a shorter one, and a value with leading or trailing whitespace is SecretPadded. A missing Secret or key is SecretNotFound or SecretKeyMissing, and nothing is sent until it appears. secretRef excludes generate and secretName, and is refused for none and private_key_jwt. It needs a server that serves control-plane contract 8; an older one leaves the resource Unsupported.

A private_key_jwt client authenticates with an RFC 7523 §2.2 assertion signed by a key it publishes at jwksUri. For that method jwksUri is required, and secret.generate is refused.

Second factor and identity providers

spec.secondFactor is the client's second-factor policy. It narrows its organization's policy, which narrows the deployment's, and can never loosen it: it can remove methods, require a factor, shorten rememberForSeconds, spare in fewer zones, always challenge, or add rules that narrow it for sign-ins outside or inside named network zones, from a new network or from a new browser. Passkey sign-in with no password is open only where the deployment, the organization and the client all set passwordless: true. A policy that states more than the levels above allow is refused, and the resource reports Refused naming the field and the bound. See Multi-Factor Authentication.

  secondFactor:
    required: true
    methods: [passkey]
    rules:
      - name: off-site
        outsideZones: [office]
        require: true

spec.identityProviders lists the identity providers in the client's organization that its users may use, by slug. A provider the client does not list is not offered to it, and none is listed by default. allowSignIn lets a user whose account at the provider is already known, or links to an existing account, sign in through it. allowSignUp lets an account at the provider that no user holds create one, and still needs the deployment's auth.registration.enabled. See Social sign-in.

  identityProviders:
    - slug: google
      allowSignIn: true
      allowSignUp: true

Spec

spec.accessTokenTtlSeconds

Type: integer | Optional

Access token lifetime in seconds.

spec.allowRefresh

Type: boolean | Optional

Whether refresh tokens may be issued to this client.

Defaults to false.

spec.clientId

Type: string | Required

Stable, unique OAuth2 client_id. Immutable.

spec.deletionPolicy

Type: string | Optional

What deleting this resource does to the client it registered. Retain leaves the client registered and the Secret holding its credential in place, so deleting a namespace cannot take a live client with it. Delete removes the client from the server, and the Secret goes with the resource. A removal that fails keeps the resource, in phase Deleting, until it succeeds.

Defaults to "Retain".

One of: Retain, Delete.

spec.displayName

Type: string | Optional

Human-readable name shown on consent screens.

spec.frontchannelLogoutSessionRequired

Type: boolean | Optional

The client requires the iss and sid query parameters on the front-channel logout request. It is SKIPPED when no sid is available, rather than sent a request it cannot act on.

spec.frontchannelLogoutUri

Type: string | Optional

RP URL the provider renders in an iframe to log this client out (OIDC Front-Channel Logout 1.0 §2). Validated as a redirection target: the browser fetches it, not the server.

spec.grantTypes

Type: []string | Optional

OAuth2 grant types this client may use.

spec.idTokenSignedResponseAlg

Type: string | Optional

JWS algorithm this client's ID tokens are signed with (RFC 7591 id_token_signed_response_alg). Omit to accept the algorithm the deployment signs with.

An organization held to FAPI 2.0 accepts only the algorithms that profile permits (§5.4.1), which this build narrows to ES256. That cannot be expressed here — the org's FAPI flag is server state, not part of this resource — so the server refuses the apply, and the resource reports Ready False with reason Refused and phase Error.

One of: ES256, RS256.

spec.idTokenTtlSeconds

Type: integer | Optional

ID token lifetime in seconds.

spec.identityProviders

Type: []object | Optional

The identity providers in this client's organization its users may sign in or sign up with, each named by its IdentityProvider's slug. A provider not listed is not offered to this client.

FieldTypeRequiredDefaultDescription
allowSignInbooleanNofalseWhether a user whose account at the provider is already known, or links to an existing account, may sign in through it.
allowSignUpbooleanNofalseWhether an account at the provider that no user holds may create one. The organization's own sign-up setting still applies.
slugstringYes—The slug of an IdentityProvider, or of an LDAPDirectory, in the client's organization.

spec.jwksUri

Type: string | Optional

HTTPS URL where this client publishes its own public keys (RFC 7591 jwks_uri). Required for tokenEndpointAuthMethod 'private_key_jwt', where it IS the credential: the RFC 7523 client assertion is verified against the keys served here. Also used to verify an RFC 9101 signed request object.

The server FETCHES this URL, which makes it a server-side request-forgery surface rather than a displayed link. It must be https and must not name a loopback, private, link-local or shared-address-space host; the server re-checks the resolved address at dial time and never follows redirects.

spec.ldapDirectories

Type: []object | Optional

The LDAP directories in this client's organization whose users sign in with their directory password, each named by its LDAPDirectory's slug and tried in order. An identifier no listed directory holds signs in with a local password.

FieldTypeRequiredDefaultDescription
allowSignInbooleanNofalseWhether a user whose account at the provider is already known, or links to an existing account, may sign in through it.
allowSignUpbooleanNofalseWhether an account at the provider that no user holds may create one. The organization's own sign-up setting still applies.
slugstringYes—The slug of an IdentityProvider, or of an LDAPDirectory, in the client's organization.

spec.organization

Type: string | Optional

The slug of the organization this client belongs to. Needed only where more than one organization admits the namespace in its spec.namespaces; otherwise the one that does is taken. The main organization is "default". Immutable.

spec.pkcePolicy

Type: string | Optional

PKCE enforcement. required_s256 mandates code_challenge with S256. optional and disabled both permit it without requiring it, and a challenge that is sent is verified. A public client must use PKCE whatever the policy.

If the organization this client is homed at runs in FAPI mode, only required_s256 is accepted for a client registering the authorization_code grant (FAPI 2.0 Security Profile 5.3.2.2). That cannot be expressed as a CEL rule here — the org's FAPI flag is server state, not part of this resource — so the server refuses the apply, and the resource reports Ready False with reason Refused and phase Error.

Defaults to "required_s256".

One of: required_s256, optional, disabled.

spec.postLogoutRedirectUris

Type: []string | Optional

Allowed post_logout_redirect_uri values.

spec.redirectUris

Type: []string | Optional

Allowed redirect_uri values (exact match).

spec.refreshTokenTtlSeconds

Type: integer | Optional

Refresh token lifetime in seconds. The server refuses a value longer than its issuer.refresh_token_ttl.

spec.responseTypes

Type: []string | Optional

OAuth2/OIDC response types. Only the authorization code response is served; the implicit and hybrid flows are not implemented, so no other value is accepted.

spec.scopes

Type: []string | Optional

Scopes the client is permitted to request.

spec.secondFactor

Type: object | Optional

The client's second-factor policy, narrowing its organization's. Passwordless sign-in stays closed for a client that does not open it, whatever its organization says.

FieldTypeRequiredDefaultDescription
alwaysChallengebooleanNo—Spares no sign-in, on a remembered browser or in a zone.
methods[]stringNo—The methods a user may enroll and answer with. Empty inherits the level above.
passwordlessbooleanNo—Opens sign-in with a discoverable passkey and no password. It is open only where the deployment, the organization and the client all open it.
rememberForSecondsintegerNo—Spares a user who holds a permitted factor the challenge on a browser that passed a second factor for them within this many seconds, while their password and factors are the ones it passed under. Zero inherits the level above's window.
requiredbooleanNo—Sends a user who holds no permitted factor through enrollment before a session is issued.
rules[]objectNo—Narrow this level's policy for the sign-ins that meet them. A rule only ever narrows its own level.
spareZones[]stringNo—Spares such a user the challenge from these of the deployment's network zones. Empty inherits the level above's.

spec.secret

Type: object | Optional

Client secret handling. The secret value itself is never placed in this resource; setting generate=true asks the server to mint one and return it exactly once.

Defaults to {"generate":false}.

FieldTypeRequiredDefaultDescription
generatebooleanNofalseAsk the server to generate a client secret.
secretNamestringNo—The Secret in this namespace the generated secret is written to, under the keys client-id, client-secret and issuer. Defaults to the resource's own name. The Secret is owned by this resource and is never written over one that is not; deleting it makes the operator mint a new secret and write it again.
secretRefobjectNo—The Secret in this namespace, and its key, holding a client secret the deployer chose, in place of one the server generates. The operator reads it and sends it to the server, which stores only its hash; it never writes or owns the Secret. Editing the Secret sends the new secret. It must be at least 43 printable ASCII characters with no whitespace, drawn at random.

spec.tokenEndpointAuthMethod

Type: string | Optional

Client authentication method at the token endpoint.

Defaults to "client_secret_basic".

One of: none, client_secret_basic, client_secret_post, private_key_jwt.

spec.tokenEndpointAuthSigningAlg

Type: string | Optional

JWS algorithm this client signs its private_key_jwt assertions with (RFC 7591 token_endpoint_auth_signing_alg). Omit to accept any algorithm the server supports.

It is enforced, not recorded: an assertion signed with another algorithm is refused. Pinning it is what stops a second key - one added to the published JWK set by someone else - from being usable against an unchanged registration.

One of: ES256, RS256.

Deleting a resource

spec.deletionPolicy decides what deleting the resource does, and defaults to Retain:

  • Retain keeps the client registered. The Secret is released from the resource (its owner reference is removed and it is annotated nauthera.io/retained-from), so deleting a namespace cannot take a live client or its credential with it.
  • Delete removes the client from the server, and the Secret is deleted with the resource.

A finalizer (nauthera.io/oidcclient) holds the resource until the policy has been carried out. A removal the server does not take keeps the resource in phase Deleting with reason ClientNotRemoved, and is retried. Each outcome is recorded as a Removed or Retained event.

Status

The operator writes status from the server's answer. clientId, issuer, etag, registeredAt and observedGeneration come from the server and are not changed by a refusal. lastReconciledTime moves on every reconcile.

phase is one of Pending, Ready, Error or Deleting. The Ready condition's reason says why:

ReasonPhaseMeaning
RegisteredReadyThe server holds the current generation.
RefusedErrorThe server refused the spec. The message gives its objections field by field.
UnreachablePendingThe server did not answer. The message names the endpoint and the cause.
UnsupportedPendingThe server serves a control-plane contract outside the operator's range. Nothing is written.
DriftedErrorThe client was changed or removed outside the operator. Nothing is written over the change.
NamespaceNotCoveredErrorThe resource's namespace is not in the operator's --namespaces. Nothing is sent to the server.
NamespaceNotAdmittedErrorNo organization admits the resource's namespace, or the one spec.organization names does not. Nothing is sent to the server.
OrganizationAmbiguousErrorSeveral organizations admit the namespace and spec.organization names none of them. Nothing is sent to the server.
OrganizationNotReadyPendingThe admitting organization has no issuer yet, because the server has not created it. The manifest is sent once it has one.
OrganizationChangedErrorThe server holds the client in one organization and the namespace now configures another. Delete and recreate the resource to move it.
CredentialUnavailablePendingThe operator's client certificate or key is missing, unreadable or expired.
TrustAnchorUnavailablePendingThe CA file is missing or holds no certificate.
SecretUndeliverableError or PendingThe server holds the secret, but the Secret could not be written.
ClientNotRemovedDeletingUnder deletionPolicy: Delete, the server has not removed the client yet.

kubectl get oidcclients shows the phase and the Ready condition's status and reason; -o wide adds the message. To write the declared state back over a drifted client, annotate the resource nauthera.io/restore. See Operator for what each reason asks you to check.

FieldTypeDescription
clientIdstringThe resolved client_id registered server-side.
conditions[]objectStandard Kubernetes conditions (Ready, Synced, ...).
etagstringThe server's revision of the client as the operator last wrote it. The operator compares it against the revision the server holds to tell its own change from one made elsewhere; a client changed elsewhere is reported as drifted and not written over until the manifest changes or the resource is annotated nauthera.io/restore.
issuerstringThe issuer the server homed the client at, after its default was applied: the organization whose rules the client is held to.
lastReconciledTimestringWhen the operator last reconciled the resource, whatever the outcome. A stale value means the operator is not running, not the leader, or cannot see the resource.
observedGenerationinteger.metadata.generation last reconciled by the server.
organizationstringThe slug of the organization the server holds the client in. Once set, a namespace that comes to configure another organization is refused rather than moving the client.
phasestringHigh-level lifecycle phase.
registeredAtstringWhen the client was first registered server-side.
secretFingerprintstringA non-reversible fingerprint of the client secret the server holds, as the server reports it; never the secret. The Secret written for it carries the same value in its nauthera.io/secret-fingerprint annotation, so the two can be told to match.
secretRefobjectReference to the Kubernetes Secret holding the one-time generated client secret (populated only when spec.secret.generate was true).