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

IdentityProvider

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

An IdentityProvider declares an external OpenID Connect provider, such as Google or Entra ID, or GitHub, that an organization's users may sign in and sign up with. The operator reads the client secret from the Secret spec.clientSecretRef names and sends it with the spec to the server's gRPC AdminService (UpsertIdentityProvider). The server stores the secret sealed and never returns it.

PropertyValue
API group / versionnauthera.io/v1alpha1
KindIdentityProvider
Plural / short nameidentityproviders / idp
ScopeNamespaced
Definitiondeploy/crds/identityprovider-crd.yaml in nauthera-server

A provider is offered to no client by default. Each OIDCClient opts in by listing the provider's slug under spec.identityProviders.

Example

Create the client at the provider first, then put the secret it issued into a Secret beside the resource. Use --from-literal or printf '%s', not echo, so the value has no trailing newline:

kubectl -n nauthera create secret generic google-oidc \
  --from-literal=client-secret=<the secret Google issued>
apiVersion: nauthera.io/v1alpha1
kind: IdentityProvider
metadata:
  name: google
  namespace: nauthera
spec:
  slug: google
  displayName: Google
  providerIssuer: https://accounts.google.com
  clientId: 1234567890-example.apps.googleusercontent.com
  clientSecretRef:
    name: google-oidc
    key: client-secret                      # the default
  scopes: [openid, email, profile]
  trustEmail: true
  allowedEmailDomains: [example.com]        # optional; empty admits every domain
  deletionPolicy: Retain

Once the resource is Ready, register status.redirectUri as an authorized redirect URI of the client at the provider. It is <organization issuer>/v1/auth/federation/<slug>/callback.

GitHub

GitHub issues no ID token, so it is not an OpenID Connect provider. With providerIssuer: https://github.com/login/oauth the server signs in through GitHub's OAuth 2.0 flow instead (with state and PKCE) and reads the user from GitHub's REST API: the numeric user id is the subject, the name (or login) the name, and the primary address the email, with GitHub's own verified flag, so trustEmail and allowedEmailDomains work as for any provider.

Register a GitHub App, which needs the account permission Email addresses (read), or an OAuth App, with status.redirectUri as its callback URL. Scopes default to read:user user:email; a GitHub App ignores them. GitHub reports nothing about how its user signed in, so trustMfa is refused; use spareSecondFactor if GitHub sign-ins should not meet the server's second factor. The server needs egress to github.com and api.github.com.

apiVersion: nauthera.io/v1alpha1
kind: IdentityProvider
metadata:
  name: github
  namespace: nauthera
spec:
  displayName: GitHub
  providerIssuer: https://github.com/login/oauth
  clientId: Iv23liExample
  clientSecretRef:
    name: github-oauth
  trustEmail: true

A refused code, a token the API refuses or a user without an id fails the sign-in like an unreachable provider: the audit record has the cause upstream_unavailable with GitHub's status and error code.

Offering it to a client

A client then offers the provider in its own spec:

apiVersion: nauthera.io/v1alpha1
kind: OIDCClient
metadata:
  name: example-webapp
  namespace: nauthera
spec:
  clientId: example-webapp
  # …
  identityProviders:
    - slug: google
      allowSignIn: true
      allowSignUp: true

kubectl get identityproviders shows the slug, the provider's issuer, the phase and the Ready condition's status and reason; -o wide adds the message.

Before you apply one

The operator's identity needs the identity_providers operation, and the organization and namespace, in its server.grpc.tls.allowed_clients entry. The Helm chart's default entry (certManager.grpcTLS.allowedClients) grants it in the main organization from every namespace; add a tenant's slug for providers there.

nauthera-cli create identity-provider registers a provider without Kubernetes (see nauthera-cli).

Trust settings

  • trustEmail says whether the provider's email_verified claim proves that its user controls the address. That is what lets a sign-in through the provider link to an existing account holding the address. Leave it false for a provider that lets its users claim an address they do not control.
  • allowedEmailDomains admits only accounts whose verified address is in one of the listed domains, matched exactly (a subdomain is its own entry), on sign-in and sign-up alike, accounts already linked included. Any other account is sent back to the sign-in page. It needs trustEmail: true, since only a trusted provider's verified address proves a domain; without it the server refuses the resource.
  • trustMfa lets the provider's own multi-factor sign-in stand in for the server's second factor. A sign-in whose ID token has an amr holding mfa (RFC 8176) is not asked for, or made to set up, a second factor, and its session carries amr mfa and acr aal2. For a provider that reports MFA in acr instead, list those values in mfaAcrValues, which needs trustMfa: true. A token with neither gets the server's own second factor. The auth.sign_in.completed audit record names the claim that vouched in upstream_mfa. Turn it on only for a provider that enforces the MFA it reports: Entra ID, Okta and Auth0 send amr; Google sends neither claim, and GitHub cannot be trusted for it.
  • spareSecondFactor lets a sign-in through the provider skip the server's second factor when the provider did not vouch for one: it is neither challenged nor sent to enroll, even where the deployment, the organization, the client or a rule requires a factor. It is for a provider whose users secure their own accounts but that reports nothing about how they signed in, such as Google or GitHub. Such a sign-in is single-factor: its session and tokens carry amr fed and no mfa, and its auth.sign_in.completed record says mfa_spared: identity_provider. A sign-in the provider does vouch for under trustMfa still gets mfa and aal2, and a password sign-in in the same organization still needs its factor.

Sign-up through a provider needs the client's allowSignUp and the deployment's auth.registration.enabled; see Sign up.

What the operator does

  • Registers and updates the provider, sending the spec and the client secret for every generation the server does not hold yet, and again whenever the Secret changes. After a restart the operator sends each provider once more.
  • Keeps the secret in its Secret. The resource never holds it. The server reports only a non-reversible fingerprint, in status.secretFingerprint. Editing the Secret sends the new secret.
  • Waits for the Secret. A Secret or key that is not there leaves the resource in Error under SecretNotFound or SecretKeyMissing, naming it. A value that begins or ends with whitespace, such as the newline echo leaves, is Error under SecretPadded: no provider issues one, and every provider refuses it. Nothing is sent until the Secret is fixed, and the resource is reconciled when it changes.
  • Fixes the slug and organization. spec.slug (the resource's name when unset) cannot be changed after creation, since the slug is part of the redirect URI registered at the provider. The organization follows from the resource's namespace (see Which organization a resource configures); spec.organization names it where several organizations admit the namespace, and once set cannot be changed.

Deleting a resource

spec.deletionPolicy decides, and defaults to Retain:

  • Retain leaves the provider registered, so deleting a namespace cannot take a sign-in method away from the organization's users.
  • Delete removes the provider from the server.

A finalizer (nauthera.io/identityprovider) holds the resource until that is done. A removal the server does not take keeps the resource in phase Deleting with reason ProviderNotRemoved, and is retried.

Spec

spec.allowedEmailDomains

Type: []string | Optional

The email domains whose accounts may sign in or sign up through the provider, such as example.com, matched exactly: a subdomain is its own entry. Left empty, every domain is admitted. With any listed, an account whose ID token carries no verified address in one of them is refused, an account already linked included.

spec.clientId

Type: string | Required

The client_id the server is registered under at the provider.

spec.clientSecretRef

Type: object | Required

The Secret in this namespace holding the client secret the provider issued. Editing the Secret sends the new secret to the server.

FieldTypeRequiredDefaultDescription
keystringNo"client-secret"The key within the Secret's data holding the client secret.
namestringYes—The Secret's name.

spec.deletionPolicy

Type: string | Optional

What deleting this resource does to the provider it registered. Retain leaves it registered, so deleting a namespace cannot take a sign-in method away from the organization's users. Delete removes it from the server. 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

The name shown on the provider's sign-in button.

spec.mfaAcrValues

Type: []string | Optional

The acr values the provider issues for a multi-factor sign-in, for a provider that reports it in acr rather than amr. Needs trustMfa. A value holds no Unicode whitespace.

spec.organization

Type: string | Optional

The slug of the organization this provider 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.providerIssuer

Type: string | Required

The provider's own issuer, whose discovery document the server reads (OpenID Connect Discovery 1.0 §4), such as https://accounts.google.com. It must be https with a public host; the server refuses one that is not. GitHub's, https://github.com/login/oauth, signs in through GitHub's OAuth 2.0 flow and REST API instead, since GitHub issues no ID token: its numeric user id is the subject and its primary address the email, and it cannot be trusted for MFA.

spec.scopes

Type: []string | Optional

The scopes requested from the provider. openid is always requested; left empty, the server asks for openid, email and profile. GitHub is asked for these alone, and for read:user and user:email when they are left empty.

spec.slug

Type: string | Optional

The provider's name in its callback URL and in an OIDCClient's spec.identityProviders: a lowercase letter followed by up to 62 lowercase letters, digits or dashes. Defaults to the resource's name. Immutable.

spec.spareSecondFactor

Type: boolean | Optional

Whether a sign-in through the provider skips the server's second factor, neither challenged nor sent to enroll one, when the provider did not vouch for one (trustMfa). Such a sign-in is single-factor: its session and tokens carry no amr mfa. For a provider whose users secure their own accounts but that reports nothing about how they signed in, such as Google or GitHub.

Defaults to false.

spec.trustEmail

Type: boolean | Optional

Whether the provider's email_verified claim proves that its user controls the address, which lets a sign-in through the provider link to an existing account holding that address. Leave it false for a provider that lets its users claim an address they do not control.

Defaults to false.

spec.trustMfa

Type: boolean | Optional

Whether the provider's own multi-factor sign-in stands in for the server's second factor: a sign-in whose ID token has an amr holding mfa, or an acr listed in mfaAcrValues, is not asked for a one-time password. Leave it false unless the provider enforces the MFA it reports.

Defaults to false.

Status

The operator writes status from the server's answer. status.redirectUri is the URI to register at the provider, and status.secretFingerprint identifies the client secret the server holds.

phase is one of Pending, Ready, Error or Deleting. Beside the connection reasons every resource shares (Refused, Unreachable, Unsupported, CredentialUnavailable, TrustAnchorUnavailable) and the namespace reasons (NamespaceNotAdmitted, OrganizationAmbiguous, OrganizationNotReady, OrganizationChanged), both explained in the OIDCClient reference, the Ready condition's reason is one of:

ReasonPhaseMeaning
RegisteredReadyThe server holds the current generation and the current secret. The message repeats the redirect URI to register.
NamespaceNotCoveredErrorThe resource's namespace is not in the operator's --namespaces. Nothing is sent.
SecretNotFoundErrorThe Secret spec.clientSecretRef names does not exist in the resource's namespace.
SecretKeyMissingErrorThe Secret holds nothing under the key.
SecretPaddedErrorThe value begins or ends with whitespace.
ProviderNotRemovedDeletingUnder deletionPolicy: Delete, the server has not removed the provider yet.

When sign-in through a provider fails, its auth.sign_in.completed audit record has the cause upstream_unavailable, with status and upstream_error saying whether the provider could not be reached or refused the server. invalid_client is a client ID or secret the provider does not accept: check spec.clientId and the Secret against the client registered at the provider.

FieldTypeDescription
conditions[]objectStandard Kubernetes conditions (Ready, ...).
etagstringThe server's revision of the provider as the operator last wrote it.
issuerstringThe issuer the server homed the provider at, after its default was applied.
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 provider in. Once set, a namespace that comes to configure another organization is refused rather than moving the provider.
phasestringHigh-level lifecycle phase.
redirectUristringThe redirect URI to register at the provider for this server: where the provider sends its users back after they sign in.
secretFingerprintstringA non-reversible fingerprint of the client secret the server holds, as the server reports it; never the secret.